Skip to content

Commit

Permalink
Add timestamp based Governor and Votes clock options (#347)
Browse files Browse the repository at this point in the history
  • Loading branch information
ericglau authored Apr 8, 2024
1 parent 8d9189d commit dfe6e7d
Show file tree
Hide file tree
Showing 26 changed files with 927 additions and 74 deletions.
4 changes: 4 additions & 0 deletions packages/core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 0.4.3 (2024-04-08)

- Add timestamp based Governor and Votes clock options. ([#347](https://github.com/OpenZeppelin/contracts-wizard/pull/347))

## 0.4.2 (2024-02-22)

- Add code comments for compatible OpenZeppelin Contracts versions. ([#331](https://github.com/OpenZeppelin/contracts-wizard/pull/331))
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openzeppelin/wizard",
"version": "0.4.2",
"version": "0.4.3",
"description": "A boilerplate generator to get started with OpenZeppelin Contracts",
"license": "MIT",
"repository": "github:OpenZeppelin/contracts-wizard",
Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface ContractFunction extends BaseFunction {
code: string[];
mutability: FunctionMutability;
final: boolean;
comments: string[];
}

export type FunctionKind = 'internal' | 'public';
Expand Down Expand Up @@ -154,6 +155,7 @@ export class ContractBuilder implements Contract {
code: [],
mutability: 'nonpayable',
final: false,
comments: [],
...baseFn,
};
this.functionMap.set(signature, fn);
Expand Down Expand Up @@ -192,6 +194,14 @@ export class ContractBuilder implements Contract {
}
}

setFunctionComments(comments: string[], baseFn: BaseFunction) {
const fn = this.addFunction(baseFn);
if (fn.comments.length > 0) {
throw new Error(`Function ${baseFn.name} already has comments`);
}
fn.comments = comments;
}

/**
* Note: The type in the variable is not currently transpiled, even if it refers to a contract
*/
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/erc20.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ testERC20('erc20 votes', {
votes: true,
});

testERC20('erc20 votes + blocknumber', {
votes: 'blocknumber',
});

testERC20('erc20 votes + timestamp', {
votes: 'timestamp',
});

testERC20('erc20 flashmint', {
flashmint: true,
});
Expand Down
79 changes: 79 additions & 0 deletions packages/core/src/erc20.test.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,85 @@ Generated by [AVA](https://avajs.dev).
}␊
`

## erc20 votes + blocknumber

> Snapshot 1
`// SPDX-License-Identifier: MIT␊
// Compatible with OpenZeppelin Contracts ^5.0.0␊
pragma solidity ^0.8.20;␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";␊
contract MyToken is ERC20, ERC20Permit, ERC20Votes {␊
constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {}␊
// The following functions are overrides required by Solidity.␊
function _update(address from, address to, uint256 value)␊
internal␊
override(ERC20, ERC20Votes)␊
{␊
super._update(from, to, value);␊
}␊
function nonces(address owner)␊
public␊
view␊
override(ERC20Permit, Nonces)␊
returns (uint256)␊
{␊
return super.nonces(owner);␊
}␊
}␊
`

## erc20 votes + timestamp

> Snapshot 1
`// SPDX-License-Identifier: MIT␊
// Compatible with OpenZeppelin Contracts ^5.0.0␊
pragma solidity ^0.8.20;␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";␊
contract MyToken is ERC20, ERC20Permit, ERC20Votes {␊
constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {}␊
function clock() public view override returns (uint48) {␊
return uint48(block.timestamp);␊
}␊
// solhint-disable-next-line func-name-mixedcase␊
function CLOCK_MODE() public pure override returns (string memory) {␊
return "mode=timestamp";␊
}␊
// The following functions are overrides required by Solidity.␊
function _update(address from, address to, uint256 value)␊
internal␊
override(ERC20, ERC20Votes)␊
{␊
super._update(from, to, value);␊
}␊
function nonces(address owner)␊
public␊
view␊
override(ERC20Permit, Nonces)␊
returns (uint256)␊
{␊
return super.nonces(owner);␊
}␊
}␊
`

## erc20 flashmint

> Snapshot 1
Expand Down
Binary file modified packages/core/src/erc20.test.ts.snap
Binary file not shown.
14 changes: 11 additions & 3 deletions packages/core/src/erc20.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { CommonOptions, withCommonDefaults, defaults as commonDefaults } from '.
import { setUpgradeable } from './set-upgradeable';
import { setInfo } from './set-info';
import { printContract } from './print';
import { ClockMode, clockModeDefault, setClockMode } from './set-clock-mode';

export interface ERC20Options extends CommonOptions {
name: string;
Expand All @@ -15,7 +16,11 @@ export interface ERC20Options extends CommonOptions {
premint?: string;
mintable?: boolean;
permit?: boolean;
votes?: boolean;
/**
* Whether to keep track of historical balances for voting in on-chain governance, and optionally specify the clock mode.
* Setting `true` is equivalent to 'blocknumber'. Setting a clock mode implies voting is enabled.
*/
votes?: boolean | ClockMode;
flashmint?: boolean;
}

Expand Down Expand Up @@ -87,7 +92,8 @@ export function buildERC20(opts: ERC20Options): Contract {
}

if (allOpts.votes) {
addVotes(c);
const clockMode = allOpts.votes === true ? clockModeDefault : allOpts.votes;
addVotes(c, clockMode);
}

if (allOpts.flashmint) {
Expand Down Expand Up @@ -166,7 +172,7 @@ function addPermit(c: ContractBuilder, name: string) {

}

function addVotes(c: ContractBuilder) {
function addVotes(c: ContractBuilder, clockMode: ClockMode) {
if (!c.parents.some(p => p.contract.name === 'ERC20Permit')) {
throw new Error('Missing ERC20Permit requirement for ERC20Votes');
}
Expand All @@ -180,6 +186,8 @@ function addVotes(c: ContractBuilder) {
c.addOverride({
name: 'Nonces',
}, functions.nonces);

setClockMode(c, ERC20Votes, clockMode);
}

function addFlashMint(c: ContractBuilder) {
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/erc721.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ testERC721('votes', {
votes: true,
});

testERC721('votes + blocknumber', {
votes: 'blocknumber',
});

testERC721('votes + timestamp', {
votes: 'timestamp',
});

testERC721('full upgradeable transparent', {
mintable: true,
enumerable: true,
Expand Down
77 changes: 77 additions & 0 deletions packages/core/src/erc721.test.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,83 @@ Generated by [AVA](https://avajs.dev).
}␊
`

## votes + blocknumber

> Snapshot 1
`// SPDX-License-Identifier: MIT␊
// Compatible with OpenZeppelin Contracts ^5.0.0␊
pragma solidity ^0.8.20;␊
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";␊
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";␊
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Votes.sol";␊
contract MyToken is ERC721, EIP712, ERC721Votes {␊
constructor() ERC721("MyToken", "MTK") EIP712("MyToken", "1") {}␊
// The following functions are overrides required by Solidity.␊
function _update(address to, uint256 tokenId, address auth)␊
internal␊
override(ERC721, ERC721Votes)␊
returns (address)␊
{␊
return super._update(to, tokenId, auth);␊
}␊
function _increaseBalance(address account, uint128 value)␊
internal␊
override(ERC721, ERC721Votes)␊
{␊
super._increaseBalance(account, value);␊
}␊
}␊
`

## votes + timestamp

> Snapshot 1
`// SPDX-License-Identifier: MIT␊
// Compatible with OpenZeppelin Contracts ^5.0.0␊
pragma solidity ^0.8.20;␊
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";␊
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";␊
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Votes.sol";␊
contract MyToken is ERC721, EIP712, ERC721Votes {␊
constructor() ERC721("MyToken", "MTK") EIP712("MyToken", "1") {}␊
function clock() public view override returns (uint48) {␊
return uint48(block.timestamp);␊
}␊
// solhint-disable-next-line func-name-mixedcase␊
function CLOCK_MODE() public pure override returns (string memory) {␊
return "mode=timestamp";␊
}␊
// The following functions are overrides required by Solidity.␊
function _update(address to, uint256 tokenId, address auth)␊
internal␊
override(ERC721, ERC721Votes)␊
returns (address)␊
{␊
return super._update(to, tokenId, auth);␊
}␊
function _increaseBalance(address account, uint128 value)␊
internal␊
override(ERC721, ERC721Votes)␊
{␊
super._increaseBalance(account, value);␊
}␊
}␊
`

## full upgradeable transparent

> Snapshot 1
Expand Down
Binary file modified packages/core/src/erc721.test.ts.snap
Binary file not shown.
16 changes: 12 additions & 4 deletions packages/core/src/erc721.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { CommonOptions, withCommonDefaults, defaults as commonDefaults } from '.
import { setUpgradeable } from './set-upgradeable';
import { setInfo } from './set-info';
import { printContract } from './print';
import { ClockMode, clockModeDefault, setClockMode } from './set-clock-mode';

export interface ERC721Options extends CommonOptions {
name: string;
Expand All @@ -18,7 +19,11 @@ export interface ERC721Options extends CommonOptions {
pausable?: boolean;
mintable?: boolean;
incremental?: boolean;
votes?: boolean;
/**
* Whether to keep track of individual units for voting in on-chain governance, and optionally specify the clock mode.
* Setting `true` is equivalent to 'blocknumber'. Setting a clock mode implies voting is enabled.
*/
votes?: boolean | ClockMode;
}

export const defaults: Required<ERC721Options> = {
Expand All @@ -34,7 +39,7 @@ export const defaults: Required<ERC721Options> = {
votes: false,
access: commonDefaults.access,
upgradeable: commonDefaults.upgradeable,
info: commonDefaults.info
info: commonDefaults.info,
} as const;

function withDefaults(opts: ERC721Options): Required<ERC721Options> {
Expand Down Expand Up @@ -94,7 +99,8 @@ export function buildERC721(opts: ERC721Options): Contract {
}

if (allOpts.votes) {
addVotes(c, allOpts.name);
const clockMode = allOpts.votes === true ? clockModeDefault : allOpts.votes;
addVotes(c, allOpts.name, clockMode);
}

setAccessControl(c, access);
Expand Down Expand Up @@ -181,7 +187,7 @@ function addMintable(c: ContractBuilder, access: Access, incremental = false, ur
}
}

function addVotes(c: ContractBuilder, name: string) {
function addVotes(c: ContractBuilder, name: string, clockMode: ClockMode) {
const EIP712 = {
name: 'EIP712',
path: '@openzeppelin/contracts/utils/cryptography/EIP712.sol',
Expand All @@ -196,6 +202,8 @@ function addVotes(c: ContractBuilder, name: string) {

c.addOverride(ERC721Votes, functions._update);
c.addOverride(ERC721Votes, functions._increaseBalance);

setClockMode(c, ERC721Votes, clockMode);
}

const functions = defineFunctions({
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/generate/erc20.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ERC20Options } from '../erc20';
import { accessOptions } from '../set-access-control';
import { clockModeOptions } from '../set-clock-mode';
import { infoOptions } from '../set-info';
import { upgradeableOptions } from '../set-upgradeable';
import { generateAlternatives } from './alternatives';
Expand All @@ -13,7 +14,7 @@ const blueprint = {
pausable: booleans,
mintable: booleans,
permit: booleans,
votes: booleans,
votes: [ ...booleans, ...clockModeOptions ] as const,
flashmint: booleans,
premint: ['1'],
access: accessOptions,
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/generate/erc721.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ERC721Options } from '../erc721';
import { accessOptions } from '../set-access-control';
import { clockModeOptions } from '../set-clock-mode';
import { infoOptions } from '../set-info';
import { upgradeableOptions } from '../set-upgradeable';
import { generateAlternatives } from './alternatives';
Expand All @@ -19,7 +20,7 @@ const blueprint = {
access: accessOptions,
upgradeable: upgradeableOptions,
info: infoOptions,
votes: booleans,
votes: [ ...booleans, ...clockModeOptions ] as const,
};

export function* generateERC721Options(): Generator<Required<ERC721Options>> {
Expand Down
Loading

0 comments on commit dfe6e7d

Please sign in to comment.