Skip to content

Commit a9bf317

Browse files
committed
feat: Add upgradeable GHO Token
1 parent f02f874 commit a9bf317

File tree

9 files changed

+890
-23
lines changed

9 files changed

+890
-23
lines changed

certora/gho/harness/DummyPool.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ contract DummyPool {
77
address asset,
88
uint256 blockTs
99
) internal view returns (uint256) {
10-
return 0; // will be replaced by a sammury in the spec file
10+
return 0; // will be replaced by a summary in the spec file
1111
}
1212
}

src/contracts/facilitators/aave/interestStrategy/FixedRateStrategyFactory.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ contract FixedRateStrategyFactory is VersionedInitializable, IFixedRateStrategyF
3030

3131
/**
3232
* @notice FixedRateStrategyFactory initializer
33-
* @dev asumes that the addresses provided are fixed rate deployed strategies.
33+
* @dev assumes that the addresses provided are fixed rate deployed strategies.
3434
* @param fixedRateStrategiesList List of fixed rate strategies
3535
*/
3636
function initialize(address[] memory fixedRateStrategiesList) external initializer {
+187
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// SPDX-License-Identifier: MIT-only
2+
pragma solidity ^0.8.0;
3+
4+
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
5+
6+
/**
7+
* @title UpgradeableERC20
8+
* @author Aave Labs
9+
* @notice Upgradeable version of Solmate ERC20
10+
* @dev Contract adaptations:
11+
* - Removal of domain separator optimization
12+
* - Move of name and symbol definition to initialization stage
13+
*/
14+
abstract contract UpgradeableERC20 is IERC20 {
15+
/*///////////////////////////////////////////////////////////////
16+
METADATA STORAGE
17+
//////////////////////////////////////////////////////////////*/
18+
19+
string public name;
20+
21+
string public symbol;
22+
23+
uint8 public immutable decimals;
24+
25+
/*///////////////////////////////////////////////////////////////
26+
ERC20 STORAGE
27+
//////////////////////////////////////////////////////////////*/
28+
29+
uint256 public totalSupply;
30+
31+
mapping(address => uint256) public balanceOf;
32+
33+
mapping(address => mapping(address => uint256)) public allowance;
34+
35+
/*///////////////////////////////////////////////////////////////
36+
EIP-2612 STORAGE
37+
//////////////////////////////////////////////////////////////*/
38+
39+
bytes32 public constant PERMIT_TYPEHASH =
40+
keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)');
41+
42+
mapping(address => uint256) public nonces;
43+
44+
/*///////////////////////////////////////////////////////////////
45+
CONSTRUCTOR
46+
//////////////////////////////////////////////////////////////*/
47+
48+
constructor(uint8 _decimals) {
49+
decimals = _decimals;
50+
}
51+
52+
/*///////////////////////////////////////////////////////////////
53+
INITIALIZER
54+
//////////////////////////////////////////////////////////////*/
55+
56+
function _ERC20_init(string memory _name, string memory _symbol) internal {
57+
name = _name;
58+
symbol = _symbol;
59+
}
60+
61+
/*///////////////////////////////////////////////////////////////
62+
ERC20 LOGIC
63+
//////////////////////////////////////////////////////////////*/
64+
65+
function approve(address spender, uint256 amount) public virtual returns (bool) {
66+
allowance[msg.sender][spender] = amount;
67+
68+
emit Approval(msg.sender, spender, amount);
69+
70+
return true;
71+
}
72+
73+
function transfer(address to, uint256 amount) public virtual returns (bool) {
74+
balanceOf[msg.sender] -= amount;
75+
76+
// Cannot overflow because the sum of all user
77+
// balances can't exceed the max uint256 value.
78+
unchecked {
79+
balanceOf[to] += amount;
80+
}
81+
82+
emit Transfer(msg.sender, to, amount);
83+
84+
return true;
85+
}
86+
87+
function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
88+
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
89+
90+
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
91+
92+
balanceOf[from] -= amount;
93+
94+
// Cannot overflow because the sum of all user
95+
// balances can't exceed the max uint256 value.
96+
unchecked {
97+
balanceOf[to] += amount;
98+
}
99+
100+
emit Transfer(from, to, amount);
101+
102+
return true;
103+
}
104+
105+
/*///////////////////////////////////////////////////////////////
106+
EIP-2612 LOGIC
107+
//////////////////////////////////////////////////////////////*/
108+
109+
function permit(
110+
address owner,
111+
address spender,
112+
uint256 value,
113+
uint256 deadline,
114+
uint8 v,
115+
bytes32 r,
116+
bytes32 s
117+
) public virtual {
118+
require(deadline >= block.timestamp, 'PERMIT_DEADLINE_EXPIRED');
119+
120+
// Unchecked because the only math done is incrementing
121+
// the owner's nonce which cannot realistically overflow.
122+
unchecked {
123+
bytes32 digest = keccak256(
124+
abi.encodePacked(
125+
'\x19\x01',
126+
DOMAIN_SEPARATOR(),
127+
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
128+
)
129+
);
130+
131+
address recoveredAddress = ecrecover(digest, v, r, s);
132+
133+
require(recoveredAddress != address(0) && recoveredAddress == owner, 'INVALID_SIGNER');
134+
135+
allowance[recoveredAddress][spender] = value;
136+
}
137+
138+
emit Approval(owner, spender, value);
139+
}
140+
141+
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
142+
return computeDomainSeparator();
143+
}
144+
145+
function computeDomainSeparator() internal view virtual returns (bytes32) {
146+
return
147+
keccak256(
148+
abi.encode(
149+
keccak256(
150+
'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'
151+
),
152+
keccak256(bytes(name)),
153+
keccak256('1'),
154+
block.chainid,
155+
address(this)
156+
)
157+
);
158+
}
159+
160+
/*///////////////////////////////////////////////////////////////
161+
INTERNAL MINT/BURN LOGIC
162+
//////////////////////////////////////////////////////////////*/
163+
164+
function _mint(address to, uint256 amount) internal virtual {
165+
totalSupply += amount;
166+
167+
// Cannot overflow because the sum of all user
168+
// balances can't exceed the max uint256 value.
169+
unchecked {
170+
balanceOf[to] += amount;
171+
}
172+
173+
emit Transfer(address(0), to, amount);
174+
}
175+
176+
function _burn(address from, uint256 amount) internal virtual {
177+
balanceOf[from] -= amount;
178+
179+
// Cannot underflow because a user's balance
180+
// will never be larger than the total supply.
181+
unchecked {
182+
totalSupply -= amount;
183+
}
184+
185+
emit Transfer(from, address(0), amount);
186+
}
187+
}
+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import {EnumerableSet} from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';
5+
import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol';
6+
import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol';
7+
import {UpgradeableERC20} from './UpgradeableERC20.sol';
8+
import {IGhoToken} from './interfaces/IGhoToken.sol';
9+
10+
/**
11+
* @title Upgradeable GHO Token
12+
* @author Aave Labs
13+
*/
14+
contract UpgradeableGhoToken is VersionedInitializable, UpgradeableERC20, AccessControl, IGhoToken {
15+
using EnumerableSet for EnumerableSet.AddressSet;
16+
17+
mapping(address => Facilitator) internal _facilitators;
18+
EnumerableSet.AddressSet internal _facilitatorsList;
19+
20+
/// @inheritdoc IGhoToken
21+
bytes32 public constant FACILITATOR_MANAGER_ROLE = keccak256('FACILITATOR_MANAGER_ROLE');
22+
23+
/// @inheritdoc IGhoToken
24+
bytes32 public constant BUCKET_MANAGER_ROLE = keccak256('BUCKET_MANAGER_ROLE');
25+
26+
/**
27+
* @dev Constructor
28+
*/
29+
constructor() UpgradeableERC20(18) {
30+
// Intentionally left bank
31+
}
32+
33+
/**
34+
* @dev Initializer
35+
* @param admin This is the initial holder of the default admin role
36+
*/
37+
function initialize(address admin) public virtual initializer {
38+
_ERC20_init('Gho Token', 'GHO');
39+
40+
_setupRole(DEFAULT_ADMIN_ROLE, admin);
41+
}
42+
43+
/// @inheritdoc IGhoToken
44+
function mint(address account, uint256 amount) external {
45+
require(amount > 0, 'INVALID_MINT_AMOUNT');
46+
Facilitator storage f = _facilitators[msg.sender];
47+
48+
uint256 currentBucketLevel = f.bucketLevel;
49+
uint256 newBucketLevel = currentBucketLevel + amount;
50+
require(f.bucketCapacity >= newBucketLevel, 'FACILITATOR_BUCKET_CAPACITY_EXCEEDED');
51+
f.bucketLevel = uint128(newBucketLevel);
52+
53+
_mint(account, amount);
54+
55+
emit FacilitatorBucketLevelUpdated(msg.sender, currentBucketLevel, newBucketLevel);
56+
}
57+
58+
/// @inheritdoc IGhoToken
59+
function burn(uint256 amount) external {
60+
require(amount > 0, 'INVALID_BURN_AMOUNT');
61+
62+
Facilitator storage f = _facilitators[msg.sender];
63+
uint256 currentBucketLevel = f.bucketLevel;
64+
uint256 newBucketLevel = currentBucketLevel - amount;
65+
f.bucketLevel = uint128(newBucketLevel);
66+
67+
_burn(msg.sender, amount);
68+
69+
emit FacilitatorBucketLevelUpdated(msg.sender, currentBucketLevel, newBucketLevel);
70+
}
71+
72+
/// @inheritdoc IGhoToken
73+
function addFacilitator(
74+
address facilitatorAddress,
75+
string calldata facilitatorLabel,
76+
uint128 bucketCapacity
77+
) external onlyRole(FACILITATOR_MANAGER_ROLE) {
78+
Facilitator storage facilitator = _facilitators[facilitatorAddress];
79+
require(bytes(facilitator.label).length == 0, 'FACILITATOR_ALREADY_EXISTS');
80+
require(bytes(facilitatorLabel).length > 0, 'INVALID_LABEL');
81+
82+
facilitator.label = facilitatorLabel;
83+
facilitator.bucketCapacity = bucketCapacity;
84+
85+
_facilitatorsList.add(facilitatorAddress);
86+
87+
emit FacilitatorAdded(
88+
facilitatorAddress,
89+
keccak256(abi.encodePacked(facilitatorLabel)),
90+
bucketCapacity
91+
);
92+
}
93+
94+
/// @inheritdoc IGhoToken
95+
function removeFacilitator(
96+
address facilitatorAddress
97+
) external onlyRole(FACILITATOR_MANAGER_ROLE) {
98+
require(
99+
bytes(_facilitators[facilitatorAddress].label).length > 0,
100+
'FACILITATOR_DOES_NOT_EXIST'
101+
);
102+
require(
103+
_facilitators[facilitatorAddress].bucketLevel == 0,
104+
'FACILITATOR_BUCKET_LEVEL_NOT_ZERO'
105+
);
106+
107+
delete _facilitators[facilitatorAddress];
108+
_facilitatorsList.remove(facilitatorAddress);
109+
110+
emit FacilitatorRemoved(facilitatorAddress);
111+
}
112+
113+
/// @inheritdoc IGhoToken
114+
function setFacilitatorBucketCapacity(
115+
address facilitator,
116+
uint128 newCapacity
117+
) external onlyRole(BUCKET_MANAGER_ROLE) {
118+
require(bytes(_facilitators[facilitator].label).length > 0, 'FACILITATOR_DOES_NOT_EXIST');
119+
120+
uint256 oldCapacity = _facilitators[facilitator].bucketCapacity;
121+
_facilitators[facilitator].bucketCapacity = newCapacity;
122+
123+
emit FacilitatorBucketCapacityUpdated(facilitator, oldCapacity, newCapacity);
124+
}
125+
126+
/// @inheritdoc IGhoToken
127+
function getFacilitator(address facilitator) external view returns (Facilitator memory) {
128+
return _facilitators[facilitator];
129+
}
130+
131+
/// @inheritdoc IGhoToken
132+
function getFacilitatorBucket(address facilitator) external view returns (uint256, uint256) {
133+
return (_facilitators[facilitator].bucketCapacity, _facilitators[facilitator].bucketLevel);
134+
}
135+
136+
/// @inheritdoc IGhoToken
137+
function getFacilitatorsList() external view returns (address[] memory) {
138+
return _facilitatorsList.values();
139+
}
140+
141+
/**
142+
* @notice Returns the revision number
143+
* @return The revision number
144+
*/
145+
function REVISION() public pure virtual returns (uint256) {
146+
return 1;
147+
}
148+
149+
/// @inheritdoc VersionedInitializable
150+
function getRevision() internal pure virtual override returns (uint256) {
151+
return REVISION();
152+
}
153+
}

0 commit comments

Comments
 (0)