Skip to content

Commit 29aeefd

Browse files
authored
Merge pull request #3 from fei-protocol/Add-Permit-Mint
Add permit + mint
2 parents 7ca7b74 + f2a17aa commit 29aeefd

File tree

4 files changed

+121
-8
lines changed

4 files changed

+121
-8
lines changed

contracts/core/Core.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ contract Core is ICore, Permissions {
2323
Fei _fei = new Fei(address(this));
2424
fei = IFei(address(_fei));
2525

26-
Tribe _tribe = new Tribe(address(this));
26+
Tribe _tribe = new Tribe(address(this), msg.sender);
2727
tribe = IERC20(address(_tribe));
2828
}
2929

contracts/dao/Tribe.sol

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
pragma solidity ^0.6.0;
22
pragma experimental ABIEncoderV2;
33

4-
// Forked from Compound's COMP
5-
// Reference: https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/Comp.sol
4+
// Forked from Tribeswap's UNI
5+
// Reference: https://etherscan.io/address/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984#code
66

77
contract Tribe {
88
/// @notice EIP-20 token name for this token
99
// solhint-disable-next-line const-name-snakecase
10-
string public constant name = "Fei Protocol Tribe";
10+
string public constant name = "Tribe";
1111

1212
/// @notice EIP-20 token symbol for this token
1313
// solhint-disable-next-line const-name-snakecase
@@ -19,7 +19,10 @@ contract Tribe {
1919

2020
/// @notice Total number of tokens in circulation
2121
// solhint-disable-next-line const-name-snakecase
22-
uint public constant totalSupply = 1_000_000_000e18; // 1 billion Tribe
22+
uint public totalSupply = 1_000_000_000e18; // 1 billion Tribe
23+
24+
/// @notice Address which may mint new tokens
25+
address public minter;
2326

2427
/// @notice Allowance amounts on behalf of others
2528
mapping (address => mapping (address => uint96)) internal allowances;
@@ -48,9 +51,15 @@ contract Tribe {
4851
/// @notice The EIP-712 typehash for the delegation struct used by the contract
4952
bytes32 public constant DELEGATION_TYPEHASH = keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");
5053

54+
/// @notice The EIP-712 typehash for the permit struct used by the contract
55+
bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
56+
5157
/// @notice A record of states for signing / validating signatures
5258
mapping (address => uint) public nonces;
5359

60+
/// @notice An event thats emitted when the minter address is changed
61+
event MinterChanged(address minter, address newMinter);
62+
5463
/// @notice An event thats emitted when an account changes its delegate
5564
event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);
5665

@@ -66,10 +75,45 @@ contract Tribe {
6675
/**
6776
* @notice Construct a new Tribe token
6877
* @param account The initial account to grant all the tokens
78+
* @param minter_ The account with minting ability
6979
*/
70-
constructor(address account) public {
80+
constructor(address account, address minter_) public {
7181
balances[account] = uint96(totalSupply);
7282
emit Transfer(address(0), account, totalSupply);
83+
minter = minter_;
84+
emit MinterChanged(address(0), minter);
85+
}
86+
87+
/**
88+
* @notice Change the minter address
89+
* @param minter_ The address of the new minter
90+
*/
91+
function setMinter(address minter_) external {
92+
require(msg.sender == minter, "Tribe::setMinter: only the minter can change the minter address");
93+
emit MinterChanged(minter, minter_);
94+
minter = minter_;
95+
}
96+
97+
/**
98+
* @notice Mint new tokens
99+
* @param dst The address of the destination account
100+
* @param rawAmount The number of tokens to be minted
101+
*/
102+
function mint(address dst, uint rawAmount) external {
103+
require(msg.sender == minter, "Tribe::mint: only the minter can mint");
104+
require(dst != address(0), "Tribe::mint: cannot transfer to the zero address");
105+
106+
// mint the amount
107+
uint96 amount = safe96(rawAmount, "Tribe::mint: amount exceeds 96 bits");
108+
uint96 safeSupply = safe96(totalSupply, "Tribe::mint: totalSupply exceeds 96 bits");
109+
totalSupply = add96(safeSupply, amount, "Tribe::mint: totalSupply exceeds 96 bits");
110+
111+
// transfer the amount to the recipient
112+
balances[dst] = add96(balances[dst], amount, "Tribe::mint: transfer amount overflows");
113+
emit Transfer(address(0), dst, amount);
114+
115+
// move delegates
116+
_moveDelegates(address(0), delegates[dst], amount);
73117
}
74118

75119
/**
@@ -104,6 +148,37 @@ contract Tribe {
104148
return true;
105149
}
106150

151+
/**
152+
* @notice Triggers an approval from owner to spends
153+
* @param owner The address to approve from
154+
* @param spender The address to be approved
155+
* @param rawAmount The number of tokens that are approved (2^256-1 means infinite)
156+
* @param deadline The time at which to expire the signature
157+
* @param v The recovery byte of the signature
158+
* @param r Half of the ECDSA signature pair
159+
* @param s Half of the ECDSA signature pair
160+
*/
161+
function permit(address owner, address spender, uint rawAmount, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
162+
uint96 amount;
163+
if (rawAmount == uint(-1)) {
164+
amount = uint96(-1);
165+
} else {
166+
amount = safe96(rawAmount, "Tribe::permit: amount exceeds 96 bits");
167+
}
168+
169+
bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainId(), address(this)));
170+
bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, rawAmount, nonces[owner]++, deadline));
171+
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
172+
address signatory = ecrecover(digest, v, r, s);
173+
require(signatory != address(0), "Tribe::permit: invalid signature");
174+
require(signatory == owner, "Tribe::permit: unauthorized");
175+
require(now <= deadline, "Tribe::permit: signature expired");
176+
177+
allowances[owner][spender] = amount;
178+
179+
emit Approval(owner, spender, amount);
180+
}
181+
107182
/**
108183
* @notice Get the number of tokens held by the `account`
109184
* @param account The address of the account to get the balance of

contracts/orchestration/CoreOrchestrator.sol

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,14 @@ interface IGovernanceOrchestrator {
104104
function detonate() external;
105105
}
106106

107+
interface ITribe {
108+
function setMinter(address minter_) external;
109+
}
110+
107111
// solhint-disable-next-line max-states-count
108112
contract CoreOrchestrator is Ownable {
109113
address public admin;
110-
bool private constant TEST_MODE = false;
114+
bool private constant TEST_MODE = true;
111115

112116
// ----------- Uniswap Addresses -----------
113117
address public constant ETH_USDC_UNI_PAIR = address(0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc);
@@ -317,5 +321,6 @@ contract CoreOrchestrator is Ownable {
317321
);
318322
governanceOrchestrator.detonate();
319323
core.grantGovernor(timelock);
324+
ITribe(tribe).setMinter(timelock);
320325
}
321326
}

contracts/token/Fei.sol

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,28 @@ contract Fei is IFei, ERC20, ERC20Burnable, CoreRef {
1313

1414
mapping (address => address) public override incentiveContract;
1515

16+
bytes32 public DOMAIN_SEPARATOR;
17+
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
18+
bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
19+
mapping(address => uint) public nonces;
20+
1621
/// @notice Fei token constructor
1722
/// @param core Fei Core address to reference
18-
constructor(address core) public ERC20("Fei USD", "FEI") CoreRef(core) {}
23+
constructor(address core) public ERC20("Fei USD", "FEI") CoreRef(core) {
24+
uint chainId;
25+
assembly {
26+
chainId := chainid()
27+
}
28+
DOMAIN_SEPARATOR = keccak256(
29+
abi.encode(
30+
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
31+
keccak256(bytes(name())),
32+
keccak256(bytes('1')),
33+
chainId,
34+
address(this)
35+
)
36+
);
37+
}
1938

2039
function setIncentiveContract(address account, address incentive) external override onlyGovernor {
2140
incentiveContract[account] = incentive;
@@ -69,4 +88,18 @@ contract Fei is IFei, ERC20, ERC20Burnable, CoreRef {
6988
IIncentive(allIncentive).incentivize(sender, recipient, msg.sender, amount);
7089
}
7190
}
91+
92+
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
93+
require(deadline >= block.timestamp, 'Fei: EXPIRED');
94+
bytes32 digest = keccak256(
95+
abi.encodePacked(
96+
'\x19\x01',
97+
DOMAIN_SEPARATOR,
98+
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
99+
)
100+
);
101+
address recoveredAddress = ecrecover(digest, v, r, s);
102+
require(recoveredAddress != address(0) && recoveredAddress == owner, 'Fei: INVALID_SIGNATURE');
103+
_approve(owner, spender, value);
104+
}
72105
}

0 commit comments

Comments
 (0)