Skip to content

Commit 880b9b8

Browse files
typicalHumanericglauernestognw
authored
Replace ERC20Blocklist/Allowlist with ERC20Restricted (#715)
Co-authored-by: Eric Lau <[email protected]> Co-authored-by: ernestognw <[email protected]>
1 parent b49e056 commit 880b9b8

File tree

15 files changed

+85
-80
lines changed

15 files changed

+85
-80
lines changed

.changeset/cuddly-bats-swim.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@openzeppelin/wizard-common': minor
3+
'@openzeppelin/wizard': patch
4+
'@openzeppelin/contracts-mcp': patch
5+
---
6+
7+
**Breaking changes**: Solidity Stablecoin and RWA: Change `limitations` option to `restrictions`. Replace ERC20Allowlist and ERC20Blocklist with ERC20Restricted.

packages/common/src/ai/descriptions/solidity.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export const solidityERC1155Descriptions = {
5656
export const solidityStablecoinDescriptions = {
5757
custodian:
5858
'Whether authorized accounts can freeze and unfreeze accounts for regulatory or security purposes. This feature is experimental, not audited and is subject to change.',
59-
limitations:
59+
restrictions:
6060
'Whether to restrict certain users from transferring tokens, either via allowing or blocking them. This feature is experimental, not audited and is subject to change.',
6161
};
6262

packages/core/solidity/src/generate/stablecoin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const erc20Full = {
4343
};
4444

4545
const stablecoinExtensions = {
46-
limitations: [false, 'allowlist', 'blocklist'] as const,
46+
restrictions: [false, 'allowlist', 'blocklist'] as const,
4747
custodian: booleans,
4848
upgradeable: [false] as const,
4949
};

packages/core/solidity/src/stablecoin.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,11 @@ testStablecoin('stablecoin custodian', {
9191
});
9292

9393
testStablecoin('stablecoin allowlist', {
94-
limitations: 'allowlist',
94+
restrictions: 'allowlist',
9595
});
9696

9797
testStablecoin('stablecoin blocklist', {
98-
limitations: 'blocklist',
98+
restrictions: 'blocklist',
9999
});
100100

101101
testStablecoin('stablecoin votes', {
@@ -128,7 +128,7 @@ testStablecoin('stablecoin full', {
128128
flashmint: true,
129129
crossChainBridging: 'custom',
130130
premintChainId: '10',
131-
limitations: 'allowlist',
131+
restrictions: 'allowlist',
132132
custodian: true,
133133
});
134134

@@ -153,7 +153,7 @@ testAPIEquivalence('stablecoin API full', {
153153
flashmint: true,
154154
crossChainBridging: 'custom',
155155
premintChainId: '10',
156-
limitations: 'allowlist',
156+
restrictions: 'allowlist',
157157
custodian: true,
158158
});
159159

@@ -164,6 +164,6 @@ test('stablecoin API assert defaults', async t => {
164164
test('stablecoin API isAccessControlRequired', async t => {
165165
t.is(stablecoin.isAccessControlRequired({ mintable: true }), true);
166166
t.is(stablecoin.isAccessControlRequired({ pausable: true }), true);
167-
t.is(stablecoin.isAccessControlRequired({ limitations: 'allowlist' }), true);
168-
t.is(stablecoin.isAccessControlRequired({ limitations: 'blocklist' }), true);
167+
t.is(stablecoin.isAccessControlRequired({ restrictions: 'allowlist' }), true);
168+
t.is(stablecoin.isAccessControlRequired({ restrictions: 'blocklist' }), true);
169169
});

packages/core/solidity/src/stablecoin.test.ts.md

Lines changed: 20 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -366,40 +366,37 @@ Generated by [AVA](https://avajs.dev).
366366
pragma solidity ^0.8.27;␊
367367
368368
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
369-
import {ERC20Allowlist} from "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Allowlist.sol";␊
370369
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";␊
370+
import {ERC20Restricted} from "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Restricted.sol";␊
371371
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";␊
372372
373-
contract MyStablecoin is ERC20, ERC20Permit, ERC20Allowlist, Ownable {␊
373+
contract MyStablecoin is ERC20, ERC20Permit, ERC20Restricted, Ownable {␊
374374
constructor(address initialOwner)␊
375375
ERC20("MyStablecoin", "MST")␊
376376
ERC20Permit("MyStablecoin")␊
377377
Ownable(initialOwner)␊
378378
{}␊
379+
380+
function isUserAllowed(address user) public view override returns (bool) {␊
381+
return getRestriction(user) == Restriction.ALLOWED;␊
382+
}␊
379383
380384
function allowUser(address user) public onlyOwner {␊
381385
_allowUser(user);␊
382386
}␊
383387
384388
function disallowUser(address user) public onlyOwner {␊
385-
_disallowUser(user);␊
389+
_resetUser(user);␊
386390
}␊
387391
388392
// The following functions are overrides required by Solidity.␊
389393
390394
function _update(address from, address to, uint256 value)␊
391395
internal␊
392-
override(ERC20, ERC20Allowlist)␊
396+
override(ERC20, ERC20Restricted)␊
393397
{␊
394398
super._update(from, to, value);␊
395399
}␊
396-
397-
function _approve(address owner, address spender, uint256 value, bool emitEvent)␊
398-
internal␊
399-
override(ERC20, ERC20Allowlist)␊
400-
{␊
401-
super._approve(owner, spender, value, emitEvent);␊
402-
}␊
403400
}␊
404401
`
405402

@@ -412,11 +409,11 @@ Generated by [AVA](https://avajs.dev).
412409
pragma solidity ^0.8.27;␊
413410
414411
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
415-
import {ERC20Blocklist} from "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Blocklist.sol";␊
416412
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";␊
413+
import {ERC20Restricted} from "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Restricted.sol";␊
417414
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";␊
418415
419-
contract MyStablecoin is ERC20, ERC20Permit, ERC20Blocklist, Ownable {␊
416+
contract MyStablecoin is ERC20, ERC20Permit, ERC20Restricted, Ownable {␊
420417
constructor(address initialOwner)␊
421418
ERC20("MyStablecoin", "MST")␊
422419
ERC20Permit("MyStablecoin")␊
@@ -428,24 +425,17 @@ Generated by [AVA](https://avajs.dev).
428425
}␊
429426
430427
function unblockUser(address user) public onlyOwner {␊
431-
_unblockUser(user);␊
428+
_resetUser(user);␊
432429
}␊
433430
434431
// The following functions are overrides required by Solidity.␊
435432
436433
function _update(address from, address to, uint256 value)␊
437434
internal␊
438-
override(ERC20, ERC20Blocklist)␊
435+
override(ERC20, ERC20Restricted)␊
439436
{␊
440437
super._update(from, to, value);␊
441438
}␊
442-
443-
function _approve(address owner, address spender, uint256 value, bool emitEvent)␊
444-
internal␊
445-
override(ERC20, ERC20Blocklist)␊
446-
{␊
447-
super._approve(owner, spender, value, emitEvent);␊
448-
}␊
449439
}␊
450440
`
451441

@@ -594,17 +584,17 @@ Generated by [AVA](https://avajs.dev).
594584
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";␊
595585
import {ERC1363} from "@openzeppelin/contracts/token/ERC20/extensions/ERC1363.sol";␊
596586
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
597-
import {ERC20Allowlist} from "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Allowlist.sol";␊
598587
import {ERC20Bridgeable} from "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Bridgeable.sol";␊
599588
import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";␊
600589
import {ERC20Custodian} from "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Custodian.sol";␊
601590
import {ERC20FlashMint} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20FlashMint.sol";␊
602591
import {ERC20Pausable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";␊
603592
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";␊
593+
import {ERC20Restricted} from "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Restricted.sol";␊
604594
import {ERC20Votes} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";␊
605595
import {Nonces} from "@openzeppelin/contracts/utils/Nonces.sol";␊
606596
607-
contract MyStablecoin is ERC20, ERC20Bridgeable, AccessControl, ERC20Burnable, ERC20Pausable, ERC1363, ERC20Permit, ERC20Votes, ERC20FlashMint, ERC20Custodian, ERC20Allowlist {␊
597+
contract MyStablecoin is ERC20, ERC20Bridgeable, AccessControl, ERC20Burnable, ERC20Pausable, ERC1363, ERC20Permit, ERC20Votes, ERC20FlashMint, ERC20Custodian, ERC20Restricted {␊
608598
bytes32 public constant TOKEN_BRIDGE_ROLE = keccak256("TOKEN_BRIDGE_ROLE");␊
609599
error Unauthorized();␊
610600
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");␊
@@ -646,30 +636,27 @@ Generated by [AVA](https://avajs.dev).
646636
function _isCustodian(address user) internal view override returns (bool) {␊
647637
return hasRole(CUSTODIAN_ROLE, user);␊
648638
}␊
639+
640+
function isUserAllowed(address user) public view override returns (bool) {␊
641+
return getRestriction(user) == Restriction.ALLOWED;␊
642+
}␊
649643
650644
function allowUser(address user) public onlyRole(LIMITER_ROLE) {␊
651645
_allowUser(user);␊
652646
}␊
653647
654648
function disallowUser(address user) public onlyRole(LIMITER_ROLE) {␊
655-
_disallowUser(user);␊
649+
_resetUser(user);␊
656650
}␊
657651
658652
// The following functions are overrides required by Solidity.␊
659653
660654
function _update(address from, address to, uint256 value)␊
661655
internal␊
662-
override(ERC20, ERC20Pausable, ERC20Votes, ERC20Custodian, ERC20Allowlist)␊
656+
override(ERC20, ERC20Pausable, ERC20Votes, ERC20Custodian, ERC20Restricted)␊
663657
{␊
664658
super._update(from, to, value);␊
665659
}␊
666-
667-
function _approve(address owner, address spender, uint256 value, bool emitEvent)␊
668-
internal␊
669-
override(ERC20, ERC20Allowlist)␊
670-
{␊
671-
super._approve(owner, spender, value, emitEvent);␊
672-
}␊
673660
674661
function supportsInterface(bytes4 interfaceId)␊
675662
public␊
-26 Bytes
Binary file not shown.

packages/core/solidity/src/stablecoin.ts

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ import {
1212
} from './erc20';
1313

1414
export interface StablecoinOptions extends ERC20Options {
15-
limitations?: false | 'allowlist' | 'blocklist';
15+
restrictions?: false | 'allowlist' | 'blocklist';
1616
custodian?: boolean;
1717
}
1818

1919
export const defaults: Required<StablecoinOptions> = {
2020
...erc20defaults,
2121
name: 'MyStablecoin',
2222
symbol: 'MST',
23-
limitations: false,
23+
restrictions: false,
2424
custodian: false,
2525
} as const;
2626

@@ -29,7 +29,7 @@ function withDefaults(opts: StablecoinOptions): Required<StablecoinOptions> {
2929
...withERC20Defaults(opts),
3030
name: opts.name ?? defaults.name,
3131
symbol: opts.symbol ?? defaults.symbol,
32-
limitations: opts.limitations ?? defaults.limitations,
32+
restrictions: opts.restrictions ?? defaults.restrictions,
3333
custodian: opts.custodian ?? defaults.custodian,
3434
};
3535
}
@@ -39,7 +39,7 @@ export function printStablecoin(opts: StablecoinOptions = defaults): string {
3939
}
4040

4141
export function isAccessControlRequired(opts: Partial<StablecoinOptions>): boolean {
42-
return opts.mintable || opts.limitations !== false || opts.custodian || opts.pausable || opts.upgradeable === 'uups';
42+
return opts.mintable || opts.restrictions !== false || opts.custodian || opts.pausable || opts.upgradeable === 'uups';
4343
}
4444

4545
export function buildStablecoin(opts: StablecoinOptions): Contract {
@@ -54,33 +54,37 @@ export function buildStablecoin(opts: StablecoinOptions): Contract {
5454
addCustodian(c, allOpts.access);
5555
}
5656

57-
if (allOpts.limitations) {
58-
addLimitations(c, allOpts.access, allOpts.limitations);
57+
if (allOpts.restrictions) {
58+
addRestrictions(c, allOpts.access, allOpts.restrictions);
5959
}
6060

6161
return c;
6262
}
6363

64-
function addLimitations(c: ContractBuilder, access: Access, mode: boolean | 'allowlist' | 'blocklist') {
65-
const type = mode === 'allowlist';
66-
const ERC20Limitation = {
67-
name: type ? 'ERC20Allowlist' : 'ERC20Blocklist',
68-
path: `@openzeppelin/community-contracts/token/ERC20/extensions/${type ? 'ERC20Allowlist' : 'ERC20Blocklist'}.sol`,
64+
function addRestrictions(c: ContractBuilder, access: Access, mode: 'allowlist' | 'blocklist') {
65+
const isAllowlist = mode === 'allowlist';
66+
const ERC20Restricted = {
67+
name: 'ERC20Restricted',
68+
path: `@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Restricted.sol`,
6969
};
7070

71-
c.addParent(ERC20Limitation);
72-
c.addOverride(ERC20Limitation, functions._update);
73-
c.addOverride(ERC20Limitation, functions._approve);
71+
c.addParent(ERC20Restricted);
72+
c.addOverride(ERC20Restricted, functions._update);
7473

75-
const [addFn, removeFn] = type
74+
if (isAllowlist) {
75+
c.addOverride(ERC20Restricted, functions.isUserAllowed);
76+
c.setFunctionBody([`return getRestriction(user) == Restriction.ALLOWED;`], functions.isUserAllowed);
77+
}
78+
79+
const [addFn, removeFn] = isAllowlist
7680
? [functions.allowUser, functions.disallowUser]
7781
: [functions.blockUser, functions.unblockUser];
7882

7983
requireAccessControl(c, addFn, access, 'LIMITER', 'limiter');
80-
c.addFunctionCode(`_${type ? 'allowUser' : 'blockUser'}(user);`, addFn);
84+
c.addFunctionCode(`_${isAllowlist ? 'allow' : 'block'}User(user);`, addFn);
8185

8286
requireAccessControl(c, removeFn, access, 'LIMITER', 'limiter');
83-
c.addFunctionCode(`_${type ? 'disallowUser' : 'unblockUser'}(user);`, removeFn);
87+
c.addFunctionCode(`_resetUser(user);`, removeFn);
8488
}
8589

8690
function addCustodian(c: ContractBuilder, access: Access) {
@@ -161,5 +165,12 @@ const functions = {
161165
kind: 'public' as const,
162166
args: [{ name: 'user', type: 'address' }],
163167
},
168+
169+
isUserAllowed: {
170+
kind: 'public' as const,
171+
args: [{ name: 'user', type: 'address' }],
172+
returns: ['bool'],
173+
mutability: 'view' as const,
174+
},
164175
}),
165176
};

packages/mcp/src/solidity/schemas.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,12 @@ const { upgradeable: _, ...erc20SchemaOmitUpgradeable } = erc20Schema;
102102

103103
export const stablecoinSchema = {
104104
...erc20SchemaOmitUpgradeable,
105-
limitations: z
105+
restrictions: z
106106
.literal(false)
107107
.or(z.literal('allowlist'))
108108
.or(z.literal('blocklist'))
109109
.optional()
110-
.describe(solidityStablecoinDescriptions.limitations),
110+
.describe(solidityStablecoinDescriptions.restrictions),
111111
custodian: z.boolean().optional().describe(solidityStablecoinDescriptions.custodian),
112112
} as const satisfies z.ZodRawShape;
113113

packages/mcp/src/solidity/tools/rwa.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ test('all', async t => {
5353
flashmint: true,
5454
crossChainBridging: 'custom',
5555
premintChainId: '10',
56-
limitations: 'allowlist',
56+
restrictions: 'allowlist',
5757
custodian: true,
5858
namespacePrefix: 'myProject',
5959
info: {

packages/mcp/src/solidity/tools/rwa.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export function registerSolidityRWA(server: McpServer) {
2525
crossChainBridging,
2626
access,
2727
info,
28-
limitations,
28+
restrictions,
2929
custodian,
3030
}) => {
3131
const opts: StablecoinOptions = {
@@ -43,7 +43,7 @@ export function registerSolidityRWA(server: McpServer) {
4343
crossChainBridging,
4444
access,
4545
info,
46-
limitations,
46+
restrictions,
4747
custodian,
4848
};
4949
return {

0 commit comments

Comments
 (0)