Compound DAO and Ring DAO

Created Diff never expires
92 removals
331 lines
89 additions
326 lines
pragma solidity ^0.5.16;
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma experimental ABIEncoderV2;
pragma experimental ABIEncoderV2;


// Forked from Compound
// See https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/GovernorAlpha.sol
contract GovernorAlpha {
contract GovernorAlpha {
/// @notice The name of this contract
/// @notice The name of this contract
string public constant name = "Compound Governor Alpha";
// solhint-disable-next-line const-name-snakecase
string public constant name = "Ring Governor Alpha";


/// @notice The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed
/// @notice The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed
function quorumVotes() public pure returns (uint) { return 400000e18; } // 400,000 = 4% of Comp
function quorumVotes() public pure returns (uint) { return 25000000e18; } // 25,000,000 = 2.5% of Ring


/// @notice The number of votes required in order for a voter to become a proposer
/// @notice The number of votes required in order for a voter to become a proposer
function proposalThreshold() public pure returns (uint) { return 100000e18; } // 100,000 = 1% of Comp
function proposalThreshold() public pure returns (uint) { return 2500000e18; } // 2,500,000 = .25% of Ring


/// @notice The maximum number of actions that can be included in a proposal
/// @notice The maximum number of actions that can be included in a proposal
function proposalMaxOperations() public pure returns (uint) { return 10; } // 10 actions
function proposalMaxOperations() public pure returns (uint) { return 10; } // 10 actions


/// @notice The delay before voting on a proposal may take place, once proposed
/// @notice The delay before voting on a proposal may take place, once proposed
function votingDelay() public pure returns (uint) { return 1; } // 1 block
function votingDelay() public pure returns (uint) { return 3333; } // ~0.5 days in blocks (assuming 13s blocks)


/// @notice The duration of voting on a proposal, in blocks
/// @notice The duration of voting on a proposal, in blocks
function votingPeriod() public pure returns (uint) { return 17280; } // ~3 days in blocks (assuming 15s blocks)
function votingPeriod() public pure returns (uint) { return 10000; } // ~1.5 days in blocks (assuming 13s blocks)


/// @notice The address of the Compound Protocol Timelock
/// @notice The address of the Ring Protocol Timelock
TimelockInterface public timelock;
TimelockInterface public timelock;


/// @notice The address of the Compound governance token
/// @notice The address of the Ring governance token
CompInterface public comp;
RingInterface public ring;


/// @notice The address of the Governor Guardian
/// @notice The address of the Governor Guardian
address public guardian;
address public guardian;


/// @notice The total number of proposals
/// @notice The total number of proposals
uint public proposalCount;
uint public proposalCount;


struct Proposal {
struct Proposal {
/// @notice Unique id for looking up a proposal
uint id;
uint id;


/// @notice Creator of the proposal
address proposer;
address proposer;


/// @notice The timestamp that the proposal will be available for execution, set once the vote succeeds
uint eta;
uint eta;


/// @notice the ordered list of target addresses for calls to be made
address[] targets;
address[] targets;


/// @notice The ordered list of values (i.e. msg.value) to be passed to the calls to be made
uint[] values;
uint[] values;


/// @notice The ordered list of function signatures to be called
string[] signatures;
string[] signatures;


/// @notice The ordered list of calldata to be passed to each call
bytes[] calldatas;
bytes[] calldatas;


/// @notice The block at which voting begins: holders must delegate their votes prior to this block
uint startBlock;
uint startBlock;


/// @notice The block at which voting ends: votes must be cast prior to this block
uint endBlock;
uint endBlock;


/// @notice Current number of votes in favor of this proposal
uint forVotes;
uint forVotes;


/// @notice Current number of votes in opposition to this proposal
uint againstVotes;
uint againstVotes;


/// @notice Flag marking whether the proposal has been canceled
bool canceled;
bool canceled;


/// @notice Flag marking whether the proposal has been executed
bool executed;
bool executed;


/// @notice Receipts of ballots for the entire set of voters
mapping (address => Receipt) receipts;
mapping (address => Receipt) receipts;
}
}


/// @notice Ballot receipt record for a voter
/// @notice Ballot receipt record for a voter
struct Receipt {
struct Receipt {
/// @notice Whether or not a vote has been cast
bool hasVoted;
bool hasVoted;


/// @notice Whether or not the voter supports the proposal
bool support;
bool support;


/// @notice The number of votes the voter had, which were cast
uint96 votes;
uint96 votes;
}
}


/// @notice Possible states that a proposal may be in
/// @notice Possible states that a proposal may be in
enum ProposalState {
enum ProposalState {
Pending,
Pending,
Active,
Active,
Canceled,
Canceled,
Defeated,
Defeated,
Succeeded,
Succeeded,
Queued,
Queued,
Expired,
Expired,
Executed
Executed
}
}


/// @notice The official record of all proposals ever proposed
/// @notice The official record of all proposals ever proposed
mapping (uint => Proposal) public proposals;
mapping (uint => Proposal) public proposals;


/// @notice The latest proposal for each proposer
/// @notice The latest proposal for each proposer
mapping (address => uint) public latestProposalIds;
mapping (address => uint) public latestProposalIds;


/// @notice The EIP-712 typehash for the contract's domain
/// @notice The EIP-712 typehash for the contract's domain
bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");


/// @notice The EIP-712 typehash for the ballot struct used by the contract
/// @notice The EIP-712 typehash for the ballot struct used by the contract
bytes32 public constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,bool support)");
bytes32 public constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,bool support)");


/// @notice An event emitted when a new proposal is created
/// @notice An event emitted when a new proposal is created
event ProposalCreated(uint id, address proposer, address[] targets, uint[] values, string[] signatures, bytes[] calldatas, uint startBlock, uint endBlock, string description);
event ProposalCreated(uint id, address proposer, address[] targets, uint[] values, string[] signatures, bytes[] calldatas, uint startBlock, uint endBlock, string description);


/// @notice An event emitted when a vote has been cast on a proposal
/// @notice An event emitted when a vote has been cast on a proposal
event VoteCast(address voter, uint proposalId, bool support, uint votes);
event VoteCast(address voter, uint proposalId, bool support, uint votes);


/// @notice An event emitted when a proposal has been canceled
/// @notice An event emitted when a proposal has been canceled
event ProposalCanceled(uint id);
event ProposalCanceled(uint id);


/// @notice An event emitted when a proposal has been queued in the Timelock
/// @notice An event emitted when a proposal has been queued in the Timelock
event ProposalQueued(uint id, uint eta);
event ProposalQueued(uint id, uint eta);


/// @notice An event emitted when a proposal has been executed in the Timelock
/// @notice An event emitted when a proposal has been executed in the Timelock
event ProposalExecuted(uint id);
event ProposalExecuted(uint id);


constructor(address timelock_, address comp_, address guardian_) public {
constructor(address timelock_, address ring_, address guardian_) {
timelock = TimelockInterface(timelock_);
timelock = TimelockInterface(timelock_);
comp = CompInterface(comp_);
ring = RingInterface(ring_);
guardian = guardian_;
guardian = guardian_;
}
}


function propose(address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas, string memory description) public returns (uint) {
function propose(address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas, string memory description) public returns (uint) {
require(comp.getPriorVotes(msg.sender, sub256(block.number, 1)) > proposalThreshold(), "GovernorAlpha::propose: proposer votes below proposal threshold");
require(ring.getPriorVotes(msg.sender, sub256(block.number, 1)) > proposalThreshold(), "GovernorAlpha: proposer votes below proposal threshold");
require(targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length, "GovernorAlpha::propose: proposal function information arity mismatch");
require(targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length, "GovernorAlpha: proposal function information arity mismatch");
require(targets.length != 0, "GovernorAlpha::propose: must provide actions");
require(targets.length != 0, "GovernorAlpha: must provide actions");
require(targets.length <= proposalMaxOperations(), "GovernorAlpha::propose: too many actions");
require(targets.length <= proposalMaxOperations(), "GovernorAlpha: too many actions");


uint latestProposalId = latestProposalIds[msg.sender];
uint latestProposalId = latestProposalIds[msg.sender];
if (latestProposalId != 0) {
if (latestProposalId != 0) {
ProposalState proposersLatestProposalState = state(latestProposalId);
ProposalState proposersLatestProposalState = state(latestProposalId);
require(proposersLatestProposalState != ProposalState.Active, "GovernorAlpha::propose: one live proposal per proposer, found an already active proposal");
require(proposersLatestProposalState != ProposalState.Active, "GovernorAlpha: one live proposal per proposer, found an already active proposal");
require(proposersLatestProposalState != ProposalState.Pending, "GovernorAlpha::propose: one live proposal per proposer, found an already pending proposal");
require(proposersLatestProposalState != ProposalState.Pending, "GovernorAlpha: one live proposal per proposer, found an already pending proposal");
}
}


uint startBlock = add256(block.number, votingDelay());
uint startBlock = add256(block.number, votingDelay());
uint endBlock = add256(startBlock, votingPeriod());
uint endBlock = add256(startBlock, votingPeriod());


proposalCount++;
proposalCount++;
Proposal memory newProposal = Proposal({

id: proposalCount,
Proposal storage newProposal = proposals[proposalCount];
proposer: msg.sender,
newProposal.id = proposalCount;
eta: 0,
newProposal.proposer = msg.sender;
targets: targets,
newProposal.eta = 0;
values: values,
newProposal.targets = targets;
signatures: signatures,
newProposal.values = values;
calldatas: calldatas,
newProposal.signatures = signatures;
startBlock: startBlock,
newProposal.calldatas = calldatas;
endBlock: endBlock,
newProposal.startBlock = startBlock;
forVotes: 0,
newProposal.endBlock = endBlock;
againstVotes: 0,
newProposal.forVotes = 0;
canceled: false,
newProposal.againstVotes = 0;
executed: false
newProposal.canceled = false;
});
newProposal.executed = false;


proposals[newProposal.id] = newProposal;
latestProposalIds[newProposal.proposer] = newProposal.id;
latestProposalIds[newProposal.proposer] = newProposal.id;


emit ProposalCreated(newProposal.id, msg.sender, targets, values, signatures, calldatas, startBlock, endBlock, description);
emit ProposalCreated(newProposal.id, msg.sender, targets, values, signatures, calldatas, startBlock, endBlock, description);
return newProposal.id;
return newProposal.id;
}
}


function queue(uint proposalId) public {
function queue(uint proposalId) public {
require(state(proposalId) == ProposalState.Succeeded, "GovernorAlpha::queue: proposal can only be queued if it is succeeded");
require(state(proposalId) == ProposalState.Succeeded, "GovernorAlpha: proposal can only be queued if it is succeeded");
Proposal storage proposal = proposals[proposalId];
Proposal storage proposal = proposals[proposalId];
// solhint-disable-next-line not-rely-on-time
uint eta = add256(block.timestamp, timelock.delay());
uint eta = add256(block.timestamp, timelock.delay());
for (uint i = 0; i < proposal.targets.length; i++) {
for (uint i = 0; i < proposal.targets.length; i++) {
_queueOrRevert(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], eta);
_queueOrRevert(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], eta);
}
}
proposal.eta = eta;
proposal.eta = eta;
emit ProposalQueued(proposalId, eta);
emit ProposalQueued(proposalId, eta);
}
}


function _queueOrRevert(address target, uint value, string memory signature, bytes memory data, uint eta) internal {
function _queueOrRevert(address target, uint value, string memory signature, bytes memory data, uint eta) internal {
require(!timelock.queuedTransactions(keccak256(abi.encode(target, value, signature, data, eta))), "GovernorAlpha::_queueOrRevert: proposal action already queued at eta");
require(!timelock.queuedTransactions(keccak256(abi.encode(target, value, signature, data, eta))), "GovernorAlpha: proposal action already queued at eta");
timelock.queueTransaction(target, value, signature, data, eta);
timelock.queueTransaction(target, value, signature, data, eta);
}
}


function execute(uint proposalId) public payable {
function execute(uint proposalId) public payable {
require(state(proposalId) == ProposalState.Queued, "GovernorAlpha::execute: proposal can only be executed if it is queued");
require(state(proposalId) == ProposalState.Queued, "GovernorAlpha: proposal can only be executed if it is queued");
Proposal storage proposal = proposals[proposalId];
Proposal storage proposal = proposals[proposalId];
proposal.executed = true;
proposal.executed = true;
for (uint i = 0; i < proposal.targets.length; i++) {
for (uint i = 0; i < proposal.targets.length; i++) {
timelock.executeTransaction.value(proposal.values[i])(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], proposal.eta);
timelock.executeTransaction{value : proposal.values[i]}(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], proposal.eta);
}
}
emit ProposalExecuted(proposalId);
emit ProposalExecuted(proposalId);
}
}


function cancel(uint proposalId) public {
function cancel(uint proposalId) public {
ProposalState state = state(proposalId);
ProposalState _state = state(proposalId);
require(state != ProposalState.Executed, "GovernorAlpha::cancel: cannot cancel executed proposal");
require(_state == ProposalState.Active || _state == ProposalState.Pending, "GovernorAlpha: can only cancel Active or Pending Proposal");


Proposal storage proposal = proposals[proposalId];
Proposal storage proposal = proposals[proposalId];
require(msg.sender == guardian || comp.getPriorVotes(proposal.proposer, sub256(block.number, 1)) < proposalThreshold(), "GovernorAlpha::cancel: proposer above threshold");
require(msg.sender == guardian || ring.getPriorVotes(proposal.proposer, sub256(block.number, 1)) < proposalThreshold(), "GovernorAlpha: proposer above threshold");


proposal.canceled = true;
proposal.canceled = true;
for (uint i = 0; i < proposal.targets.length; i++) {
for (uint i = 0; i < proposal.targets.length; i++) {
timelock.cancelTransaction(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], proposal.eta);
timelock.cancelTransaction(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], proposal.eta);
}
}


emit ProposalCanceled(proposalId);
emit ProposalCanceled(proposalId);
}
}


function getActions(uint proposalId) public view returns (address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas) {
function getActions(uint proposalId) public view returns (address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas) {
Proposal storage p = proposals[proposalId];
Proposal storage p = proposals[proposalId];
return (p.targets, p.values, p.signatures, p.calldatas);
return (p.targets, p.values, p.signatures, p.calldatas);
}
}


function getReceipt(uint proposalId, address voter) public view returns (Receipt memory) {
function getReceipt(uint proposalId, address voter) public view returns (Receipt memory) {
return proposals[proposalId].receipts[voter];
return proposals[proposalId].receipts[voter];
}
}


function state(uint proposalId) public view returns (ProposalState) {
function state(uint proposalId) public view returns (ProposalState) {
require(proposalCount >= proposalId && proposalId > 0, "GovernorAlpha::state: invalid proposal id");
require(proposalCount >= proposalId && proposalId > 0, "GovernorAlpha: invalid proposal id");
Proposal storage proposal = proposals[proposalId];
Proposal storage proposal = proposals[proposalId];
if (proposal.canceled) {
if (proposal.canceled) {
return ProposalState.Canceled;
return ProposalState.Canceled;
} else if (block.number <= proposal.startBlock) {
} else if (block.number <= proposal.startBlock) {
return ProposalState.Pending;
return ProposalState.Pending;
} else if (block.number <= proposal.endBlock) {
} else if (block.number <= proposal.endBlock) {
return ProposalState.Active;
return ProposalState.Active;
} else if (proposal.forVotes <= proposal.againstVotes || proposal.forVotes < quorumVotes()) {
} else if (proposal.forVotes <= proposal.againstVotes || proposal.forVotes < quorumVotes()) {
return ProposalState.Defeated;
return ProposalState.Defeated;
} else if (proposal.eta == 0) {
} else if (proposal.eta == 0) {
return ProposalState.Succeeded;
return ProposalState.Succeeded;
} else if (proposal.executed) {
} else if (proposal.executed) {
return ProposalState.Executed;
return ProposalState.Executed;
// solhint-disable-next-line not-rely-on-time
} else if (block.timestamp >= add256(proposal.eta, timelock.GRACE_PERIOD())) {
} else if (block.timestamp >= add256(proposal.eta, timelock.GRACE_PERIOD())) {
return ProposalState.Expired;
return ProposalState.Expired;
} else {
} else {
return ProposalState.Queued;
return ProposalState.Queued;
}
}
}
}


function castVote(uint proposalId, bool support) public {
function castVote(uint proposalId, bool support) public {
return _castVote(msg.sender, proposalId, support);
return _castVote(msg.sender, proposalId, support);
}
}


function castVoteBySig(uint proposalId, bool support, uint8 v, bytes32 r, bytes32 s) public {
function castVoteBySig(uint proposalId, bool support, uint8 v, bytes32 r, bytes32 s) public {
bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainId(), address(this)));
bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainId(), address(this)));
bytes32 structHash = keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support));
bytes32 structHash = keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support));
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
address signatory = ecrecover(digest, v, r, s);
address signatory = ecrecover(digest, v, r, s);
require(signatory != address(0), "GovernorAlpha::castVoteBySig: invalid signature");
require(signatory != address(0), "GovernorAlpha: invalid signature");
return _castVote(signatory, proposalId, support);
return _castVote(signatory, proposalId, support);
}
}


function _castVote(address voter, uint proposalId, bool support) internal {
function _castVote(address voter, uint proposalId, bool support) internal {
require(state(proposalId) == ProposalState.Active, "GovernorAlpha::_castVote: voting is closed");
require(state(proposalId) == ProposalState.Active, "GovernorAlpha: voting is closed");
Proposal storage proposal = proposals[proposalId];
Proposal storage proposal = proposals[proposalId];
Receipt storage receipt = proposal.receipts[voter];
Receipt storage receipt = proposal.receipts[voter];
require(receipt.hasVoted == false, "GovernorAlpha::_castVote: voter already voted");
require(receipt.hasVoted == false, "GovernorAlpha: voter already voted");
uint96 votes = comp.getPriorVotes(voter, proposal.startBlock);
uint96 votes = ring.getPriorVotes(voter, proposal.startBlock);


if (support) {
if (support) {
proposal.forVotes = add256(proposal.forVotes, votes);
proposal.forVotes = add256(proposal.forVotes, votes);
} else {
} else {
proposal.againstVotes = add256(proposal.againstVotes, votes);
proposal.againstVotes = add256(proposal.againstVotes, votes);
}
}


receipt.hasVoted = true;
receipt.hasVoted = true;
receipt.support = support;
receipt.support = support;
receipt.votes = votes;
receipt.votes = votes;


emit VoteCast(voter, proposalId, support, votes);
emit VoteCast(voter, proposalId, support, votes);
}
}


function __acceptAdmin() public {
function __acceptAdmin() public {
require(msg.sender == guardian, "GovernorAlpha::__acceptAdmin: sender must be gov guardian");
require(msg.sender == guardian, "GovernorAlpha: sender must be gov guardian");
timelock.acceptAdmin();
timelock.acceptAdmin();
}
}


function __abdicate() public {
function __abdicate() public {
require(msg.sender == guardian, "GovernorAlpha::__abdicate: sender must be gov guardian");
require(msg.sender == guardian, "GovernorAlpha: sender must be gov guardian");
guardian = address(0);
guardian = address(0);
}

function __transferGuardian(address newGuardian) public {
require(msg.sender == guardian, "GovernorAlpha: sender must be gov guardian");
guardian = newGuardian;
}
}


function __queueSetTimelockPendingAdmin(address newPendingAdmin, uint eta) public {
function __queueSetTimelockPendingAdmin(address newPendingAdmin, uint eta) public {
require(msg.sender == guardian, "GovernorAlpha::__queueSetTimelockPendingAdmin: sender must be gov guardian");
require(msg.sender == guardian, "GovernorAlpha: sender must be gov guardian");
timelock.queueTransaction(address(timelock), 0, "setPendingAdmin(address)", abi.encode(newPendingAdmin), eta);
timelock.queueTransaction(address(timelock), 0, "setPendingAdmin(address)", abi.encode(newPendingAdmin), eta);
}
}


function __executeSetTimelockPendingAdmin(address newPendingAdmin, uint eta) public {
function __executeSetTimelockPendingAdmin(address newPendingAdmin, uint eta) public {
require(msg.sender == guardian, "GovernorAlpha::__executeSetTimelockPendingAdmin: sender must be gov guardian");
require(msg.sender == guardian, "GovernorAlpha: sender must be gov guardian");
timelock.executeTransaction(address(timelock), 0, "setPendingAdmin(address)", abi.encode(newPendingAdmin), eta);
timelock.executeTransaction(address(timelock), 0, "setPendingAdmin(address)", abi.encode(newPendingAdmin), eta);
}
}


function add256(uint256 a, uint256 b) internal pure returns (uint) {
function add256(uint256 a, uint256 b) internal pure returns (uint) {
uint c = a + b;
uint c = a + b;
require(c >= a, "addition overflow");
require(c >= a, "addition overflow");
return c;
return c;
}
}


function sub256(uint256 a, uint256 b) internal pure returns (uint) {
function sub256(uint256 a, uint256 b) internal pure returns (uint) {
require(b <= a, "subtraction underflow");
require(b <= a, "subtraction underflow");
return a - b;
return a - b;
}
}


function getChainId() internal pure returns (uint) {
function getChainId() internal pure returns (uint) {
uint chainId;
uint chainId;
// solhint-disable-next-line no-inline-assembly
assembly { chainId := chainid() }
assembly { chainId := chainid() }
return chainId;
return chainId;
}
}
}
}


interface TimelockInterface {
interface TimelockInterface {
function delay() external view returns (uint);
function delay() external view returns (uint);
// solhint-disable-next-line func-name-mixedcase
function GRACE_PERIOD() external view returns (uint);
function GRACE_PERIOD() external view returns (uint);
function acceptAdmin() external;
function acceptAdmin() external;
function queuedTransactions(bytes32 hash) external view returns (bool);
function queuedTransactions(bytes32 hash) external view returns (bool);
function queueTransaction(address target, uint value, string calldata signature, bytes calldata data, uint eta) external returns (bytes32);
function queueTransaction(address target, uint value, string calldata signature, bytes calldata data, uint eta) external returns (bytes32);
function cancelTransaction(address target, uint value, string calldata signature, bytes calldata data, uint eta) external;
function cancelTransaction(address target, uint value, string calldata signature, bytes calldata data, uint eta) external;
function executeTransaction(address target, uint value, string calldata signature, bytes calldata data, uint eta) external payable returns (bytes memory);
function executeTransaction(address target, uint value, string calldata signature, bytes calldata data, uint eta) external payable returns (bytes memory);
}
}


interface CompInterface {
interface RingInterface {
function getPriorVotes(address account, uint blockNumber) external view returns (uint96);
function getPriorVotes(address account, uint blockNumber) external view returns (uint96);
}
}