Skip to content

Commit 9d67083

Browse files
committed
Cache address resolution from the Controller (#430)
Having a Controller contract is great in that we can sync addresses across the protocol from a single place, but it involves a roundtrip of CALL opcodes to get the addresses each time they are used. This change creates a local cache within each contract to store the list of contract addresses and refresh it when the Controller changes any contract address. This mechanism relies on an external party calling the `syncAllContracts` function.
1 parent e113336 commit 9d67083

File tree

4 files changed

+94
-17
lines changed

4 files changed

+94
-17
lines changed

contracts/governance/Managed.sol

Lines changed: 77 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
pragma solidity ^0.7.3;
44

5-
import "./IManaged.sol";
65
import "./IController.sol";
6+
77
import "../curation/ICuration.sol";
88
import "../epochs/IEpochManager.sol";
99
import "../rewards/IRewardsManager.sol";
@@ -12,19 +12,33 @@ import "../token/IGraphToken.sol";
1212

1313
/**
1414
* @title Graph Managed contract
15-
* @dev The Managed contract provides an interface for contracts to interact with the Controller
15+
* @dev The Managed contract provides an interface to interact with the Controller.
16+
* It also provides local caching for contract addresses. This mechanism relies on calling the
17+
* public `syncAllContracts()` function whenever a contract changes in the controller.
18+
*
1619
* Inspired by Livepeer:
1720
* https://github.com/livepeer/protocol/blob/streamflow/contracts/Controller.sol
1821
*/
1922
contract Managed {
23+
// -- State --
24+
2025
// Controller that contract is registered with
2126
IController public controller;
22-
mapping(bytes32 => address) public addressCache;
27+
mapping(bytes32 => address) private addressCache;
2328
uint256[10] private __gap;
2429

30+
// -- Events --
31+
2532
event ParameterUpdated(string param);
2633
event SetController(address controller);
2734

35+
/**
36+
* @dev Emitted when contract with `nameHash` is synced to `contractAddress`.
37+
*/
38+
event ContractSynced(bytes32 indexed nameHash, address contractAddress);
39+
40+
// -- Modifiers --
41+
2842
function _notPartialPaused() internal view {
2943
require(!controller.paused(), "Paused");
3044
require(!controller.partialPaused(), "Partial-paused");
@@ -38,6 +52,10 @@ contract Managed {
3852
require(msg.sender == controller.getGovernor(), "Caller must be Controller governor");
3953
}
4054

55+
function _onlyController() internal view {
56+
require(msg.sender == address(controller), "Caller must be Controller");
57+
}
58+
4159
modifier notPartialPaused {
4260
_notPartialPaused();
4361
_;
@@ -48,26 +66,29 @@ contract Managed {
4866
_;
4967
}
5068

51-
// Check if sender is controller
69+
// Check if sender is controller.
5270
modifier onlyController() {
53-
require(msg.sender == address(controller), "Caller must be Controller");
71+
_onlyController();
5472
_;
5573
}
5674

75+
// Check if sender is the governor.
5776
modifier onlyGovernor() {
5877
_onlyGovernor();
5978
_;
6079
}
6180

81+
// -- Functions --
82+
6283
/**
63-
* @dev Initialize the controller
84+
* @dev Initialize the controller.
6485
*/
6586
function _initialize(address _controller) internal {
6687
_setController(_controller);
6788
}
6889

6990
/**
70-
* @notice Set Controller. Only callable by current controller
91+
* @notice Set Controller. Only callable by current controller.
7192
* @param _controller Controller contract address
7293
*/
7394
function setController(address _controller) external onlyController {
@@ -85,42 +106,81 @@ contract Managed {
85106
}
86107

87108
/**
88-
* @dev Return Curation interface
109+
* @dev Return Curation interface.
89110
* @return Curation contract registered with Controller
90111
*/
91112
function curation() internal view returns (ICuration) {
92-
return ICuration(controller.getContractProxy(keccak256("Curation")));
113+
return ICuration(_resolveContract(keccak256("Curation")));
93114
}
94115

95116
/**
96-
* @dev Return EpochManager interface
117+
* @dev Return EpochManager interface.
97118
* @return Epoch manager contract registered with Controller
98119
*/
99120
function epochManager() internal view returns (IEpochManager) {
100-
return IEpochManager(controller.getContractProxy(keccak256("EpochManager")));
121+
return IEpochManager(_resolveContract(keccak256("EpochManager")));
101122
}
102123

103124
/**
104-
* @dev Return RewardsManager interface
125+
* @dev Return RewardsManager interface.
105126
* @return Rewards manager contract registered with Controller
106127
*/
107128
function rewardsManager() internal view returns (IRewardsManager) {
108-
return IRewardsManager(controller.getContractProxy(keccak256("RewardsManager")));
129+
return IRewardsManager(_resolveContract(keccak256("RewardsManager")));
109130
}
110131

111132
/**
112-
* @dev Return Staking interface
133+
* @dev Return Staking interface.
113134
* @return Staking contract registered with Controller
114135
*/
115136
function staking() internal view returns (IStaking) {
116-
return IStaking(controller.getContractProxy(keccak256("Staking")));
137+
return IStaking(_resolveContract(keccak256("Staking")));
117138
}
118139

119140
/**
120-
* @dev Return GraphToken interface
141+
* @dev Return GraphToken interface.
121142
* @return Graph token contract registered with Controller
122143
*/
123144
function graphToken() internal view returns (IGraphToken) {
124-
return IGraphToken(controller.getContractProxy(keccak256("GraphToken")));
145+
return IGraphToken(_resolveContract(keccak256("GraphToken")));
146+
}
147+
148+
/**
149+
* @dev Resolve a contract address from the cache or the Controller if not found.
150+
* @return Address of the contract
151+
*/
152+
function _resolveContract(bytes32 _nameHash) internal view returns (address) {
153+
address contractAddress = addressCache[_nameHash];
154+
if (contractAddress == address(0)) {
155+
contractAddress = controller.getContractProxy(_nameHash);
156+
}
157+
return contractAddress;
158+
}
159+
160+
/**
161+
* @dev Cache a contract address from the Controller registry.
162+
* @param _name Name of the contract to sync into the cache
163+
*/
164+
function _syncContract(string memory _name) internal {
165+
bytes32 nameHash = keccak256(abi.encodePacked(_name));
166+
address contractAddress = controller.getContractProxy(nameHash);
167+
if (addressCache[nameHash] != contractAddress) {
168+
addressCache[nameHash] = contractAddress;
169+
emit ContractSynced(nameHash, contractAddress);
170+
}
171+
}
172+
173+
/**
174+
* @dev Sync protocol contract addresses from the Controller registry.
175+
* This function will cache all the contracts using the latest addresses
176+
* Anyone can call the function whenever a Proxy contract change in the
177+
* controller to ensure the protocol is using the latest version
178+
*/
179+
function syncAllContracts() external {
180+
_syncContract("Curation");
181+
_syncContract("EpochManager");
182+
_syncContract("RewardsManager");
183+
_syncContract("Staking");
184+
_syncContract("GraphToken");
125185
}
126186
}

test/curation/curation.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,8 @@ describe('Curation', () => {
311311
await controller
312312
.connect(governor.signer)
313313
.setContractProxy(utils.id('Staking'), stakingMock.address)
314+
await curation.syncAllContracts()
315+
314316
const tx = curation
315317
.connect(stakingMock.signer)
316318
.collect(subgraphDeploymentID, tokensToCollect)
@@ -332,6 +334,8 @@ describe('Curation', () => {
332334
await controller
333335
.connect(governor.signer)
334336
.setContractProxy(utils.id('Staking'), stakingMock.address)
337+
await curation.syncAllContracts()
338+
335339
await shouldCollect(toGRT('1'))
336340
await shouldCollect(toGRT('10'))
337341
await shouldCollect(toGRT('100'))
@@ -343,6 +347,7 @@ describe('Curation', () => {
343347
await controller
344348
.connect(governor.signer)
345349
.setContractProxy(utils.id('Staking'), stakingMock.address)
350+
await curation.syncAllContracts()
346351

347352
// Collect increase the pool reserves
348353
await shouldCollect(toGRT('100'))

test/lib/fixtures.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ export class NetworkFixture {
5858
await controller.setContractProxy(utils.id('ServiceRegistry'), serviceRegistry.address)
5959

6060
// Setup contracts
61+
await curation.connect(deployer).syncAllContracts()
62+
await gns.connect(deployer).syncAllContracts()
63+
await serviceRegistry.connect(deployer).syncAllContracts()
64+
await disputeManager.connect(deployer).syncAllContracts()
65+
await rewardsManager.connect(deployer).syncAllContracts()
66+
await staking.connect(deployer).syncAllContracts()
67+
6168
await staking.connect(deployer).setSlasher(slasherAddress, true)
6269
await grt.connect(deployer).addMinter(rewardsManager.address)
6370
await gns.connect(deployer).approveAll()

test/staking/delegation.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
toGRT,
1616
toBN,
1717
Account,
18+
advanceBlock,
1819
} from '../lib/testHelpers'
1920

2021
const { AddressZero, HashZero } = constants
@@ -445,6 +446,9 @@ describe('Staking::Delegation', () => {
445446
})
446447

447448
it('should undelegate properly when multiple delegations', async function () {
449+
// Use long enough epochs to avoid jumping to the next epoch involuntarily on our test
450+
await epochManager.setEpochLength(toBN((60 * 60) / 15))
451+
448452
await shouldDelegate(delegator, toGRT('1234'))
449453
await shouldDelegate(delegator, toGRT('100'))
450454
await shouldDelegate(delegator, toGRT('50'))
@@ -460,6 +464,7 @@ describe('Staking::Delegation', () => {
460464
await staking.setDelegationUnbondingPeriod('2')
461465
await shouldDelegate(delegator, toGRT('100'))
462466
await shouldUndelegate(delegator, toGRT('50'))
467+
await advanceBlock()
463468
await advanceToNextEpoch(epochManager) // epoch 1
464469
await advanceToNextEpoch(epochManager) // epoch 2
465470
await shouldUndelegate(delegator, toGRT('10'))

0 commit comments

Comments
 (0)