Skip to content

Commit 4f63f43

Browse files
authored
Contracts sdk erc20 (#199)
* erc20 bases: add erc20votes as base contract * add alt signature-mint to work with erc20permit * add comments * run prettier * voting: delegation by signature logic * run prettier * fix failing PR checks * fix imports and minor changes * run prettier * update base erc20 and erc20permit * remove mint function from Drop contracts * overload collectPriceOnClaim to support MintRequest param in signature minting * tests: ERC20Base, ERC20Vote, ERC20SignatureMint, ERC20SignatureMintVote * update tests: ERC20SignatureMint * tests: ERC20Drop, ERC20DropVote * run prettier * remove unused
1 parent d3f61ab commit 4f63f43

37 files changed

+10086
-3
lines changed

contracts/base/ERC20Base.sol

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
import "../openzeppelin-presets/token/ERC20/extensions/ERC20Permit.sol";
5+
6+
import "../extension/ContractMetadata.sol";
7+
import "../extension/Multicall.sol";
8+
import "../extension/Ownable.sol";
9+
10+
/**
11+
* The `ERC20Base` smart contract implements the ERC20 standard.
12+
* It includes the following additions to standard ERC20 logic:
13+
*
14+
* - Ability to mint & burn tokens via the provided `mint` & `burn` functions.
15+
*
16+
* - Ownership of the contract, with the ability to restrict certain functions to
17+
* only be called by the contract's owner.
18+
*
19+
* - Multicall capability to perform multiple actions atomically
20+
*
21+
* - EIP 2612 compliance: See {ERC20-permit} method, which can be used to change an account's ERC20 allowance by
22+
* presenting a message signed by the account.
23+
*/
24+
25+
contract ERC20Base is ContractMetadata, Multicall, Ownable, ERC20Permit {
26+
/*//////////////////////////////////////////////////////////////
27+
Constructor
28+
//////////////////////////////////////////////////////////////*/
29+
30+
constructor(
31+
string memory _name,
32+
string memory _symbol,
33+
string memory _contractURI
34+
) ERC20Permit(_name, _symbol) {
35+
_setupContractURI(_contractURI);
36+
_setupOwner(msg.sender);
37+
}
38+
39+
/*//////////////////////////////////////////////////////////////
40+
Minting logic
41+
//////////////////////////////////////////////////////////////*/
42+
43+
/**
44+
* @notice Lets an authorized address mint tokens to a recipient.
45+
* @dev The logic in the `_canMint` function determines whether the caller is authorized to mint tokens.
46+
*
47+
* @param _to The recipient of the tokens to mint.
48+
* @param _amount Quantity of tokens to mint.
49+
*/
50+
function mint(address _to, uint256 _amount) public virtual {
51+
require(_canMint(), "Not authorized to mint.");
52+
require(_amount != 0, "Minting zero tokens.");
53+
54+
_mint(_to, _amount);
55+
}
56+
57+
/**
58+
* @notice Lets an owner a given amount of their tokens.
59+
* @dev Caller should own the `_amount` of tokens.
60+
*
61+
* @param _amount The number of tokens to burn.
62+
*/
63+
function burn(uint256 _amount) external virtual {
64+
require(balanceOf(msg.sender) >= _amount, "not enough balance");
65+
_burn(msg.sender, _amount);
66+
}
67+
68+
/*//////////////////////////////////////////////////////////////
69+
Internal (overrideable) functions
70+
//////////////////////////////////////////////////////////////*/
71+
72+
/// @dev Returns whether contract metadata can be set in the given execution context.
73+
function _canSetContractURI() internal view virtual override returns (bool) {
74+
return msg.sender == owner();
75+
}
76+
77+
/// @dev Returns whether tokens can be minted in the given execution context.
78+
function _canMint() internal view virtual returns (bool) {
79+
return msg.sender == owner();
80+
}
81+
82+
/// @dev Returns whether owner can be set in the given execution context.
83+
function _canSetOwner() internal view virtual override returns (bool) {
84+
return msg.sender == owner();
85+
}
86+
}

contracts/base/ERC20Drop.sol

+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
import "../openzeppelin-presets/token/ERC20/extensions/ERC20Permit.sol";
5+
6+
import "../extension/ContractMetadata.sol";
7+
import "../extension/Multicall.sol";
8+
import "../extension/Ownable.sol";
9+
import "../extension/PrimarySale.sol";
10+
import { SignatureMintERC20 } from "../extension/SignatureMintERC20.sol";
11+
import "../extension/DropSinglePhase.sol";
12+
13+
import "../lib/CurrencyTransferLib.sol";
14+
15+
/**
16+
* BASE: ERC20
17+
* EXTENSION: SignatureMintERC20, DropSinglePhase
18+
*
19+
* The `ERC20Drop` contract uses the `ERC20Base` contract, along with the `SignatureMintERC20` and `DropSinglePhase` extensions.
20+
*
21+
* The 'signature minting' mechanism in the `SignatureMintERC20` extension is a way for a contract admin to authorize
22+
* an external party's request to mint tokens on the admin's contract. At a high level, this means you can authorize
23+
* some external party to mint tokens on your contract, and specify what exactly will be minted by that external party.
24+
*
25+
* The `drop` mechanism in the `DropSinglePhase` extension is a distribution mechanism for tokens. It lets
26+
* you set restrictions such as a price to charge, an allowlist etc. when an address atttempts to mint tokens.
27+
*
28+
*/
29+
30+
contract ERC20Drop is
31+
ContractMetadata,
32+
Multicall,
33+
Ownable,
34+
ERC20Permit,
35+
PrimarySale,
36+
SignatureMintERC20,
37+
DropSinglePhase
38+
{
39+
/*//////////////////////////////////////////////////////////////
40+
Constructor
41+
//////////////////////////////////////////////////////////////*/
42+
43+
constructor(
44+
string memory _name,
45+
string memory _symbol,
46+
string memory _contractURI,
47+
address _primarySaleRecipient
48+
) ERC20Permit(_name, _symbol) {
49+
_setupContractURI(_contractURI);
50+
_setupOwner(msg.sender);
51+
_setupPrimarySaleRecipient(_primarySaleRecipient);
52+
}
53+
54+
/*//////////////////////////////////////////////////////////////
55+
Signature minting logic
56+
//////////////////////////////////////////////////////////////*/
57+
58+
/**
59+
* @notice Mints tokens according to the provided mint request.
60+
*
61+
* @param _req The payload / mint request.
62+
* @param _signature The signature produced by an account signing the mint request.
63+
*/
64+
function mintWithSignature(MintRequest calldata _req, bytes calldata _signature)
65+
external
66+
payable
67+
virtual
68+
returns (address signer)
69+
{
70+
require(_req.quantity > 0, "Minting zero tokens.");
71+
72+
// Verify and process payload.
73+
signer = _processRequest(_req, _signature);
74+
75+
/**
76+
* Get receiver of tokens.
77+
*
78+
* Note: If `_req.to == address(0)`, a `mintWithSignature` transaction sitting in the
79+
* mempool can be frontrun by copying the input data, since the minted tokens
80+
* will be sent to the `_msgSender()` in this case.
81+
*/
82+
address receiver = _req.to == address(0) ? msg.sender : _req.to;
83+
84+
// Collect price
85+
collectPriceOnClaim(_req);
86+
87+
// Mint tokens.
88+
_mint(receiver, _req.quantity);
89+
90+
emit TokensMintedWithSignature(signer, receiver, _req);
91+
}
92+
93+
/*//////////////////////////////////////////////////////////////
94+
ERC20 logic
95+
//////////////////////////////////////////////////////////////*/
96+
97+
/**
98+
* @notice Lets an owner a given amount of their tokens.
99+
* @dev Caller should own the `_amount` of tokens.
100+
*
101+
* @param _amount The number of tokens to burn.
102+
*/
103+
function burn(uint256 _amount) external virtual {
104+
require(balanceOf(msg.sender) >= _amount, "not enough balance");
105+
_burn(msg.sender, _amount);
106+
}
107+
108+
/*//////////////////////////////////////////////////////////////
109+
Internal (overrideable) functions
110+
//////////////////////////////////////////////////////////////*/
111+
112+
/// @dev Collects and distributes the primary sale value of tokens being minted with signature.
113+
function collectPriceOnClaim(MintRequest calldata _req) internal virtual {
114+
if (_req.pricePerToken == 0) {
115+
return;
116+
}
117+
118+
uint256 totalPrice = (_req.quantity * _req.pricePerToken) / 1 ether;
119+
require(totalPrice > 0, "quantity too low");
120+
121+
if (_req.currency == CurrencyTransferLib.NATIVE_TOKEN) {
122+
require(msg.value == totalPrice, "must send total price.");
123+
}
124+
125+
CurrencyTransferLib.transferCurrency(_req.currency, msg.sender, _req.primarySaleRecipient, totalPrice);
126+
}
127+
128+
/// @dev Collects and distributes the primary sale value of tokens being claimed.
129+
function collectPriceOnClaim(
130+
uint256 _quantityToClaim,
131+
address _currency,
132+
uint256 _pricePerToken
133+
) internal virtual override {
134+
if (_pricePerToken == 0) {
135+
return;
136+
}
137+
138+
uint256 totalPrice = (_quantityToClaim * _pricePerToken) / 1 ether;
139+
require(totalPrice > 0, "quantity too low");
140+
141+
if (_currency == CurrencyTransferLib.NATIVE_TOKEN) {
142+
require(msg.value == totalPrice, "Must send total price.");
143+
}
144+
145+
CurrencyTransferLib.transferCurrency(_currency, msg.sender, primarySaleRecipient(), totalPrice);
146+
}
147+
148+
/// @dev Transfers the tokens being claimed.
149+
function transferTokensOnClaim(address _to, uint256 _quantityBeingClaimed)
150+
internal
151+
virtual
152+
override
153+
returns (uint256)
154+
{
155+
_mint(_to, _quantityBeingClaimed);
156+
return 0;
157+
}
158+
159+
/// @dev Checks whether platform fee info can be set in the given execution context.
160+
function _canSetClaimConditions() internal view virtual override returns (bool) {
161+
return msg.sender == owner();
162+
}
163+
164+
/// @dev Returns whether contract metadata can be set in the given execution context.
165+
function _canSetContractURI() internal view virtual override returns (bool) {
166+
return msg.sender == owner();
167+
}
168+
169+
/// @dev Returns whether tokens can be minted in the given execution context.
170+
function _canMint() internal view virtual returns (bool) {
171+
return msg.sender == owner();
172+
}
173+
174+
/// @dev Returns whether owner can be set in the given execution context.
175+
function _canSetOwner() internal view virtual override returns (bool) {
176+
return msg.sender == owner();
177+
}
178+
179+
/// @dev Returns whether a given address is authorized to sign mint requests.
180+
function _canSignMintRequest(address _signer) internal view virtual override returns (bool) {
181+
return _signer == owner();
182+
}
183+
184+
/// @dev Returns whether primary sale recipient can be set in the given execution context.
185+
function _canSetPrimarySaleRecipient() internal view virtual override returns (bool) {
186+
return msg.sender == owner();
187+
}
188+
}

0 commit comments

Comments
 (0)