Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contracts/core/Core.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ contract Core is ICore, Permissions {
Fei _fei = new Fei(address(this));
fei = IFei(address(_fei));

Tribe _tribe = new Tribe(address(this));
Tribe _tribe = new Tribe(address(this), msg.sender);
tribe = IERC20(address(_tribe));
}

Expand Down
85 changes: 80 additions & 5 deletions contracts/dao/Tribe.sol
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;

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

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

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

/// @notice Total number of tokens in circulation
// solhint-disable-next-line const-name-snakecase
uint public constant totalSupply = 1_000_000_000e18; // 1 billion Tribe
uint public totalSupply = 1_000_000_000e18; // 1 billion Tribe

/// @notice Address which may mint new tokens
address public minter;

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

/// @notice The EIP-712 typehash for the permit struct used by the contract
bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");

/// @notice A record of states for signing / validating signatures
mapping (address => uint) public nonces;

/// @notice An event thats emitted when the minter address is changed
event MinterChanged(address minter, address newMinter);

/// @notice An event thats emitted when an account changes its delegate
event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);

Expand All @@ -66,10 +75,45 @@ contract Tribe {
/**
* @notice Construct a new Tribe token
* @param account The initial account to grant all the tokens
* @param minter_ The account with minting ability
*/
constructor(address account) public {
constructor(address account, address minter_) public {
balances[account] = uint96(totalSupply);
emit Transfer(address(0), account, totalSupply);
minter = minter_;
emit MinterChanged(address(0), minter);
}

/**
* @notice Change the minter address
* @param minter_ The address of the new minter
*/
function setMinter(address minter_) external {
require(msg.sender == minter, "Tribe::setMinter: only the minter can change the minter address");
emit MinterChanged(minter, minter_);
minter = minter_;
}

/**
* @notice Mint new tokens
* @param dst The address of the destination account
* @param rawAmount The number of tokens to be minted
*/
function mint(address dst, uint rawAmount) external {
require(msg.sender == minter, "Tribe::mint: only the minter can mint");
require(dst != address(0), "Tribe::mint: cannot transfer to the zero address");

// mint the amount
uint96 amount = safe96(rawAmount, "Tribe::mint: amount exceeds 96 bits");
uint96 safeSupply = safe96(totalSupply, "Tribe::mint: totalSupply exceeds 96 bits");
totalSupply = add96(safeSupply, amount, "Tribe::mint: totalSupply exceeds 96 bits");

// transfer the amount to the recipient
balances[dst] = add96(balances[dst], amount, "Tribe::mint: transfer amount overflows");
emit Transfer(address(0), dst, amount);

// move delegates
_moveDelegates(address(0), delegates[dst], amount);
}

/**
Expand Down Expand Up @@ -104,6 +148,37 @@ contract Tribe {
return true;
}

/**
* @notice Triggers an approval from owner to spends
* @param owner The address to approve from
* @param spender The address to be approved
* @param rawAmount The number of tokens that are approved (2^256-1 means infinite)
* @param deadline The time at which to expire the signature
* @param v The recovery byte of the signature
* @param r Half of the ECDSA signature pair
* @param s Half of the ECDSA signature pair
*/
function permit(address owner, address spender, uint rawAmount, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
uint96 amount;
if (rawAmount == uint(-1)) {
amount = uint96(-1);
} else {
amount = safe96(rawAmount, "Tribe::permit: amount exceeds 96 bits");
}

bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainId(), address(this)));
bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, rawAmount, nonces[owner]++, deadline));
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
address signatory = ecrecover(digest, v, r, s);
require(signatory != address(0), "Tribe::permit: invalid signature");
require(signatory == owner, "Tribe::permit: unauthorized");
require(now <= deadline, "Tribe::permit: signature expired");

allowances[owner][spender] = amount;

emit Approval(owner, spender, amount);
}

/**
* @notice Get the number of tokens held by the `account`
* @param account The address of the account to get the balance of
Expand Down
7 changes: 6 additions & 1 deletion contracts/orchestration/CoreOrchestrator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,14 @@ interface IGovernanceOrchestrator {
function detonate() external;
}

interface ITribe {
function setMinter(address minter_) external;
}

// solhint-disable-next-line max-states-count
contract CoreOrchestrator is Ownable {
address public admin;
bool private constant TEST_MODE = false;
bool private constant TEST_MODE = true;

// ----------- Uniswap Addresses -----------
address public constant ETH_USDC_UNI_PAIR = address(0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc);
Expand Down Expand Up @@ -317,5 +321,6 @@ contract CoreOrchestrator is Ownable {
);
governanceOrchestrator.detonate();
core.grantGovernor(timelock);
ITribe(tribe).setMinter(timelock);
}
}
35 changes: 34 additions & 1 deletion contracts/token/Fei.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,28 @@ contract Fei is IFei, ERC20, ERC20Burnable, CoreRef {

mapping (address => address) public override incentiveContract;

bytes32 public DOMAIN_SEPARATOR;
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
mapping(address => uint) public nonces;

/// @notice Fei token constructor
/// @param core Fei Core address to reference
constructor(address core) public ERC20("Fei USD", "FEI") CoreRef(core) {}
constructor(address core) public ERC20("Fei USD", "FEI") CoreRef(core) {
uint chainId;
assembly {
chainId := chainid()
}
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes(name())),
keccak256(bytes('1')),
chainId,
address(this)
)
);
}

function setIncentiveContract(address account, address incentive) external override onlyGovernor {
incentiveContract[account] = incentive;
Expand Down Expand Up @@ -69,4 +88,18 @@ contract Fei is IFei, ERC20, ERC20Burnable, CoreRef {
IIncentive(allIncentive).incentivize(sender, recipient, msg.sender, amount);
}
}

function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
require(deadline >= block.timestamp, 'Fei: EXPIRED');
bytes32 digest = keccak256(
abi.encodePacked(
'\x19\x01',
DOMAIN_SEPARATOR,
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
)
);
address recoveredAddress = ecrecover(digest, v, r, s);
require(recoveredAddress != address(0) && recoveredAddress == owner, 'Fei: INVALID_SIGNATURE');
_approve(owner, spender, value);
}
}