mirror of
https://github.com/Instadapp/dsa-governance.git
synced 2024-07-29 22:27:52 +00:00
385 lines
16 KiB
Solidity
385 lines
16 KiB
Solidity
pragma solidity ^0.7.0;
|
|
pragma experimental ABIEncoderV2;
|
|
|
|
import { TokenDelegateStorageV1, TokenEvents} from "./TokenInterfaces.sol";
|
|
import { SafeMath } from "./SafeMath.sol";
|
|
|
|
// TODO @thrilok209 @KaymasJain - Rename it
|
|
contract TokenDelegate is TokenDelegateStorageV1, TokenEvents {
|
|
/// @notice EIP-20 token decimals for this token
|
|
uint8 public constant decimals = 18;
|
|
|
|
/// @notice Minimum time between mints
|
|
uint32 public constant minimumTimeBetweenMints = 1 days * 7; // TODO @thrilok209 @KaymasJain - Replace it
|
|
|
|
/// @notice Cap on the percentage of totalSupply that can be minted at each mint
|
|
uint8 public constant mintCap = 2; // TODO @thrilok209 @KaymasJain - Replace it
|
|
|
|
/// @notice The EIP-712 typehash for the contract's domain
|
|
bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
|
|
|
|
/// @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)");
|
|
|
|
|
|
// TODO @mubaris - add comment of each parameter.
|
|
function initialize(address account, address minter_, uint mintingAllowedAfter_, bool transferPaused_) public {
|
|
require(mintingAllowedAfter == 0, "Token::initialize: can only initialize once");
|
|
require(address(minter) == address(0), "Token::initialize: can only initialize once");
|
|
require(mintingAllowedAfter_ >= block.timestamp, "Token::constructor: minting can only begin after deployment");
|
|
require(msg.sender == minter, "Token::initialize: admin only");
|
|
// TODO @mubaris - anything else to add in require statements?
|
|
|
|
balances[account] = uint96(totalSupply);
|
|
emit Transfer(address(0), account, totalSupply);
|
|
minter = minter_;
|
|
emit MinterChanged(address(0), minter);
|
|
mintingAllowedAfter = mintingAllowedAfter_;
|
|
transferPaused = transferPaused_;
|
|
|
|
if (transferPaused) {
|
|
emit TransferPaused(msg.sender);
|
|
} else {
|
|
emit TransferUnpaused(msg.sender);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @notice Change the minter address
|
|
* @param minter_ The address of the new minter
|
|
*/
|
|
function setMinter(address minter_) external {
|
|
require(msg.sender == minter, "Tkn::setMinter: only the minter can change the minter address");
|
|
emit MinterChanged(minter, minter_);
|
|
minter = minter_;
|
|
}
|
|
|
|
/**
|
|
* @notice Pause the token transfer
|
|
*/
|
|
function pauseTransfer() external {
|
|
require(msg.sender == minter, "Tkn::pauseTransfer: only the minter can pause token transfer");
|
|
transferPaused = true;
|
|
emit TransferPaused(msg.sender);
|
|
}
|
|
|
|
/**
|
|
* @notice Unpause the token transfer
|
|
*/
|
|
function unpauseTransfer() external {
|
|
require(msg.sender == minter, "Tkn::unpauseTransfer: only the minter can unpause token transfer");
|
|
transferPaused = false;
|
|
emit TransferUnpaused(msg.sender);
|
|
}
|
|
|
|
/**
|
|
* @notice Token name and symbol change
|
|
*/
|
|
// TODO @KaymasJain - Looks good? And should we have two functions or single?
|
|
function changeNameAndSymbol(string calldata name_, string calldata symbol_) external {
|
|
require(msg.sender == minter, "Tkn::changeNameAndSymbol: only the minter can change token name and symbol");
|
|
require(bytes(name_).length > 0, "Tkn::changeNameAndSymbol: name_name_ length invaild");
|
|
require(bytes(symbol_).length > 0, "Tkn::changeNameAndSymbol: symbol_ length invaild");
|
|
|
|
emit ChangedNameAndSymbol(name, name_, symbol, symbol_);
|
|
name = name_;
|
|
symbol = symbol_;
|
|
}
|
|
|
|
/**
|
|
* @notice Get the number of tokens `spender` is approved to spend on behalf of `account`
|
|
* @param account The address of the account holding the funds
|
|
* @param spender The address of the account spending the funds
|
|
* @return The number of tokens approved
|
|
*/
|
|
function allowance(address account, address spender) external view returns (uint) {
|
|
return allowances[account][spender];
|
|
}
|
|
|
|
/**
|
|
* @notice Approve `spender` to transfer up to `amount` from `src`
|
|
* @dev This will overwrite the approval amount for `spender`
|
|
* and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve)
|
|
* @param spender The address of the account which may transfer tokens
|
|
* @param rawAmount The number of tokens that are approved (2^256-1 means infinite)
|
|
* @return Whether or not the approval succeeded
|
|
*/
|
|
function approve(address spender, uint rawAmount) external returns (bool) {
|
|
uint96 amount;
|
|
if (rawAmount == uint(-1)) {
|
|
amount = uint96(-1);
|
|
} else {
|
|
amount = safe96(rawAmount, "Tkn::approve: amount exceeds 96 bits");
|
|
}
|
|
|
|
allowances[msg.sender][spender] = amount;
|
|
|
|
emit Approval(msg.sender, spender, amount);
|
|
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, "Tkn::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), "Tkn::permit: invalid signature");
|
|
require(signatory == owner, "Tkn::permit: unauthorized");
|
|
require(block.timestamp <= deadline, "Tkn::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
|
|
* @return The number of tokens held
|
|
*/
|
|
function balanceOf(address account) external view returns (uint) {
|
|
return balances[account];
|
|
}
|
|
|
|
/**
|
|
* @notice Transfer `amount` tokens from `msg.sender` to `dst`
|
|
* @param dst The address of the destination account
|
|
* @param rawAmount The number of tokens to transfer
|
|
* @return Whether or not the transfer succeeded
|
|
*/
|
|
function transfer(address dst, uint rawAmount) external returns (bool) {
|
|
uint96 amount = safe96(rawAmount, "Tkn::transfer: amount exceeds 96 bits");
|
|
_transferTokens(msg.sender, dst, amount);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @notice Transfer `amount` tokens from `src` to `dst`
|
|
* @param src The address of the source account
|
|
* @param dst The address of the destination account
|
|
* @param rawAmount The number of tokens to transfer
|
|
* @return Whether or not the transfer succeeded
|
|
*/
|
|
function transferFrom(address src, address dst, uint rawAmount) external returns (bool) {
|
|
address spender = msg.sender;
|
|
uint96 spenderAllowance = allowances[src][spender];
|
|
uint96 amount = safe96(rawAmount, "Tkn::approve: amount exceeds 96 bits");
|
|
|
|
if (spender != src && spenderAllowance != uint96(-1)) {
|
|
uint96 newAllowance = sub96(spenderAllowance, amount, "Tkn::transferFrom: transfer amount exceeds spender allowance");
|
|
allowances[src][spender] = newAllowance;
|
|
|
|
emit Approval(src, spender, newAllowance);
|
|
}
|
|
|
|
_transferTokens(src, dst, amount);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @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, "Tkn::mint: only the minter can mint");
|
|
require(block.timestamp >= mintingAllowedAfter, "Uni::mint: minting not allowed yet");
|
|
require(dst != address(0), "Tkn::mint: cannot transfer to the zero address");
|
|
|
|
// record the mint
|
|
mintingAllowedAfter = SafeMath.add(block.timestamp, minimumTimeBetweenMints);
|
|
|
|
// mint the amount
|
|
uint96 amount = safe96(rawAmount, "Tkn::mint: amount exceeds 96 bits");
|
|
require(amount <= SafeMath.div(SafeMath.mul(totalSupply, mintCap), 100), "Uni::mint: exceeded mint cap");
|
|
totalSupply = safe96(SafeMath.add(totalSupply, amount), "Uni::mint: totalSupply exceeds 96 bits");
|
|
|
|
// transfer the amount to the recipient
|
|
balances[dst] = add96(balances[dst], amount, "Tkn::mint: transfer amount overflows");
|
|
emit Transfer(address(0), dst, amount);
|
|
|
|
// move delegates
|
|
_moveDelegates(address(0), delegates[dst], amount);
|
|
}
|
|
|
|
/**
|
|
* @notice Delegate votes from `msg.sender` to `delegatee`
|
|
* @param delegatee The address to delegate votes to
|
|
*/
|
|
function delegate(address delegatee) public {
|
|
return _delegate(msg.sender, delegatee);
|
|
}
|
|
|
|
/**
|
|
* @notice Delegates votes from signatory to `delegatee`
|
|
* @param delegatee The address to delegate votes to
|
|
* @param nonce The contract state required to match the signature
|
|
* @param expiry 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 delegateBySig(address delegatee, uint nonce, uint expiry, uint8 v, bytes32 r, bytes32 s) public {
|
|
bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainId(), address(this)));
|
|
bytes32 structHash = keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry));
|
|
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
|
|
address signatory = ecrecover(digest, v, r, s);
|
|
require(signatory != address(0), "Tkn::delegateBySig: invalid signature");
|
|
require(nonce == nonces[signatory]++, "Tkn::delegateBySig: invalid nonce");
|
|
require(block.timestamp <= expiry, "Tkn::delegateBySig: signature expired");
|
|
return _delegate(signatory, delegatee);
|
|
}
|
|
|
|
/**
|
|
* @notice Gets the current votes balance for `account`
|
|
* @param account The address to get votes balance
|
|
* @return The number of current votes for `account`
|
|
*/
|
|
function getCurrentVotes(address account) external view returns (uint96) {
|
|
uint32 nCheckpoints = numCheckpoints[account];
|
|
return nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0;
|
|
}
|
|
|
|
/**
|
|
* @notice Determine the prior number of votes for an account as of a block number
|
|
* @dev Block number must be a finalized block or else this function will revert to prevent misinformation.
|
|
* @param account The address of the account to check
|
|
* @param blockNumber The block number to get the vote balance at
|
|
* @return The number of votes the account had as of the given block
|
|
*/
|
|
function getPriorVotes(address account, uint blockNumber) public view returns (uint96) {
|
|
require(blockNumber < block.number, "Tkn::getPriorVotes: not yet determined");
|
|
|
|
uint32 nCheckpoints = numCheckpoints[account];
|
|
if (nCheckpoints == 0) {
|
|
return 0;
|
|
}
|
|
|
|
// First check most recent balance
|
|
if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) {
|
|
return checkpoints[account][nCheckpoints - 1].votes;
|
|
}
|
|
|
|
// Next check implicit zero balance
|
|
if (checkpoints[account][0].fromBlock > blockNumber) {
|
|
return 0;
|
|
}
|
|
|
|
uint32 lower = 0;
|
|
uint32 upper = nCheckpoints - 1;
|
|
while (upper > lower) {
|
|
uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow
|
|
Checkpoint memory cp = checkpoints[account][center];
|
|
if (cp.fromBlock == blockNumber) {
|
|
return cp.votes;
|
|
} else if (cp.fromBlock < blockNumber) {
|
|
lower = center;
|
|
} else {
|
|
upper = center - 1;
|
|
}
|
|
}
|
|
return checkpoints[account][lower].votes;
|
|
}
|
|
|
|
|
|
function _delegate(address delegator, address delegatee) internal {
|
|
address currentDelegate = delegates[delegator];
|
|
uint96 delegatorBalance = balances[delegator];
|
|
delegates[delegator] = delegatee;
|
|
|
|
emit DelegateChanged(delegator, currentDelegate, delegatee);
|
|
|
|
_moveDelegates(currentDelegate, delegatee, delegatorBalance);
|
|
}
|
|
|
|
function _transferTokens(address src, address dst, uint96 amount) internal {
|
|
require(!transferPaused, "Tkn::_transferTokens: transfer paused");
|
|
require(src != address(0), "Tkn::_transferTokens: cannot transfer from the zero address");
|
|
require(dst != address(0), "Tkn::_transferTokens: cannot transfer to the zero address");
|
|
|
|
balances[src] = sub96(balances[src], amount, "Tkn::_transferTokens: transfer amount exceeds balance");
|
|
balances[dst] = add96(balances[dst], amount, "Tkn::_transferTokens: transfer amount overflows");
|
|
emit Transfer(src, dst, amount);
|
|
|
|
_moveDelegates(delegates[src], delegates[dst], amount);
|
|
}
|
|
|
|
function _moveDelegates(address srcRep, address dstRep, uint96 amount) internal {
|
|
if (srcRep != dstRep && amount > 0) {
|
|
if (srcRep != address(0)) {
|
|
uint32 srcRepNum = numCheckpoints[srcRep];
|
|
uint96 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0;
|
|
uint96 srcRepNew = sub96(srcRepOld, amount, "Tkn::_moveVotes: vote amount underflows");
|
|
_writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew);
|
|
}
|
|
|
|
if (dstRep != address(0)) {
|
|
uint32 dstRepNum = numCheckpoints[dstRep];
|
|
uint96 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0;
|
|
uint96 dstRepNew = add96(dstRepOld, amount, "Tkn::_moveVotes: vote amount overflows");
|
|
_writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew);
|
|
}
|
|
}
|
|
}
|
|
|
|
function _writeCheckpoint(address delegatee, uint32 nCheckpoints, uint96 oldVotes, uint96 newVotes) internal {
|
|
uint32 blockNumber = safe32(block.number, "Tkn::_writeCheckpoint: block number exceeds 32 bits");
|
|
|
|
if (nCheckpoints > 0 && checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber) {
|
|
checkpoints[delegatee][nCheckpoints - 1].votes = newVotes;
|
|
} else {
|
|
checkpoints[delegatee][nCheckpoints] = Checkpoint(blockNumber, newVotes);
|
|
numCheckpoints[delegatee] = nCheckpoints + 1;
|
|
}
|
|
|
|
emit DelegateVotesChanged(delegatee, oldVotes, newVotes);
|
|
}
|
|
|
|
|
|
function safe32(uint n, string memory errorMessage) internal pure returns (uint32) {
|
|
require(n < 2**32, errorMessage);
|
|
return uint32(n);
|
|
}
|
|
|
|
function safe96(uint n, string memory errorMessage) internal pure returns (uint96) {
|
|
require(n < 2**96, errorMessage);
|
|
return uint96(n);
|
|
}
|
|
|
|
function add96(uint96 a, uint96 b, string memory errorMessage) internal pure returns (uint96) {
|
|
uint96 c = a + b;
|
|
require(c >= a, errorMessage);
|
|
return c;
|
|
}
|
|
|
|
function sub96(uint96 a, uint96 b, string memory errorMessage) internal pure returns (uint96) {
|
|
require(b <= a, errorMessage);
|
|
return a - b;
|
|
}
|
|
|
|
function getChainId() internal pure returns (uint) {
|
|
uint256 chainId;
|
|
assembly { chainId := chainid() }
|
|
return chainId;
|
|
}
|
|
} |