Skip to content

Commit ae7610b

Browse files
author
Dev Kalra
authored
[entropy] governance (#1154)
* governance * correct comment * add upgradability * remove unused lib * update governance and update logic * change specifiers * fix tests * add tests * rebase bug fixes * change error name * fix tests * no need of UUPSProxy * minimise the amount of code * separate file for authorized tests * add comment * correct comment
1 parent b2e4d56 commit ae7610b

File tree

9 files changed

+379
-10
lines changed

9 files changed

+379
-10
lines changed

target_chains/ethereum/contracts/contracts/entropy/Entropy.sol

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,14 @@ import "./EntropyState.sol";
7373
// protocol prior to the random number being revealed (i.e., prior to step (6) above). Integrators should ensure that
7474
// the user is always incentivized to reveal their random number, and that the protocol has an escape hatch for
7575
// cases where the user chooses not to reveal.
76-
contract Entropy is IEntropy, EntropyState {
77-
// TODO: Use an upgradeable proxy
78-
constructor(
76+
abstract contract Entropy is IEntropy, EntropyState {
77+
function _initialize(
78+
address admin,
7979
uint128 pythFeeInWei,
8080
address defaultProvider,
8181
bool prefillRequestStorage
82-
) {
82+
) internal {
83+
_state.admin = admin;
8384
_state.accruedPythFeesInWei = 0;
8485
_state.pythFeeInWei = pythFeeInWei;
8586
_state.defaultProvider = defaultProvider;
@@ -305,6 +306,10 @@ contract Entropy is IEntropy, EntropyState {
305306
return _state.providers[provider].feeInWei + _state.pythFeeInWei;
306307
}
307308

309+
function getPythFee() public view returns (uint128 feeAmount) {
310+
return _state.pythFeeInWei;
311+
}
312+
308313
function getAccruedPythFees()
309314
public
310315
view
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// SPDX-License-Identifier: Apache 2
2+
pragma solidity ^0.8.0;
3+
4+
import "@pythnetwork/entropy-sdk-solidity/EntropyErrors.sol";
5+
6+
import "./EntropyState.sol";
7+
8+
/**
9+
* @dev `Governance` defines a means to enacting changes to the Entropy contract.
10+
*/
11+
abstract contract EntropyGovernance is EntropyState {
12+
event AdminSet(address oldAdmin, address newAdmin);
13+
event PythFeeSet(uint oldPythFee, uint newPythFee);
14+
event DefaultProviderSet(
15+
address oldDefaultProvider,
16+
address newDefaultProvider
17+
);
18+
19+
function getAdmin() external view returns (address) {
20+
return _state.admin;
21+
}
22+
23+
/**
24+
* @dev Set the admin of the contract.
25+
*
26+
* Calls {_authoriseAdminAction}.
27+
*
28+
* Emits an {AdminSet} event.
29+
*/
30+
function setAdmin(address newAdmin) external {
31+
_authoriseAdminAction();
32+
33+
address oldAdmin = _state.admin;
34+
_state.admin = newAdmin;
35+
36+
emit AdminSet(oldAdmin, newAdmin);
37+
}
38+
39+
/**
40+
* @dev Set the Pyth fee in Wei
41+
*
42+
* Calls {_authoriseAdminAction}.
43+
*
44+
* Emits an {PythFeeSet} event.
45+
*/
46+
function setPythFee(uint128 newPythFee) external {
47+
_authoriseAdminAction();
48+
49+
uint oldPythFee = _state.pythFeeInWei;
50+
_state.pythFeeInWei = newPythFee;
51+
52+
emit PythFeeSet(oldPythFee, newPythFee);
53+
}
54+
55+
/**
56+
* @dev Set the default provider of the contract
57+
*
58+
* Calls {_authoriseAdminAction}.
59+
*
60+
* Emits an {DefaultProviderSet} event.
61+
*/
62+
function setDefaultProvider(address newDefaultProvider) external {
63+
_authoriseAdminAction();
64+
65+
address oldDefaultProvider = _state.defaultProvider;
66+
_state.defaultProvider = newDefaultProvider;
67+
68+
emit DefaultProviderSet(oldDefaultProvider, newDefaultProvider);
69+
}
70+
71+
function _authoriseAdminAction() internal virtual;
72+
}

target_chains/ethereum/contracts/contracts/entropy/EntropyState.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import "@pythnetwork/entropy-sdk-solidity/EntropyStructs.sol";
66

77
contract EntropyInternalStructs {
88
struct State {
9+
// Admin has the rights to update pyth configs
10+
address admin;
911
// Fee charged by the pyth protocol in wei.
1012
uint128 pythFeeInWei;
1113
// Total quantity of fees (in wei) earned by the pyth protocol that are currently stored in the contract.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// SPDX-License-Identifier: Apache 2
2+
pragma solidity ^0.8.0;
3+
4+
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
5+
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
6+
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
7+
import "@pythnetwork/entropy-sdk-solidity/EntropyErrors.sol";
8+
9+
import "./EntropyGovernance.sol";
10+
import "./Entropy.sol";
11+
12+
contract EntropyUpgradable is
13+
Initializable,
14+
OwnableUpgradeable,
15+
UUPSUpgradeable,
16+
Entropy,
17+
EntropyGovernance
18+
{
19+
event ContractUpgraded(
20+
address oldImplementation,
21+
address newImplementation
22+
);
23+
24+
// The contract will have an owner and an admin
25+
// The owner will have all the power over it.
26+
// The admin can set some config parameters only.
27+
function initialize(
28+
address owner,
29+
address admin,
30+
uint128 pythFeeInWei,
31+
address defaultProvider,
32+
bool prefillRequestStorage
33+
) public initializer {
34+
__Ownable_init();
35+
__UUPSUpgradeable_init();
36+
37+
Entropy._initialize(
38+
admin,
39+
pythFeeInWei,
40+
defaultProvider,
41+
prefillRequestStorage
42+
);
43+
44+
// We need to transfer the ownership from deployer to the new owner
45+
transferOwnership(owner);
46+
}
47+
48+
/// Ensures the contract cannot be uninitialized and taken over.
49+
/// @custom:oz-upgrades-unsafe-allow constructor
50+
constructor() initializer {}
51+
52+
// Only allow the owner to upgrade the proxy to a new implementation.
53+
function _authorizeUpgrade(address) internal override onlyOwner {}
54+
55+
// There are some actions which both and admin and owner can perform
56+
function _authoriseAdminAction() internal view override {
57+
if (msg.sender != owner() && msg.sender != _state.admin)
58+
revert EntropyErrors.Unauthorized();
59+
}
60+
61+
// We have not overridden these methods in Pyth contracts implementation.
62+
// But we are overriding them here because there was no owner before and
63+
// `_authorizeUpgrade` would cause a revert for these. Now we have an owner, and
64+
// because we want to test for the magic. We are overriding these methods.
65+
function upgradeTo(address newImplementation) external override onlyProxy {
66+
address oldImplementation = _getImplementation();
67+
_authorizeUpgrade(newImplementation);
68+
_upgradeToAndCallUUPS(newImplementation, new bytes(0), false);
69+
70+
magicCheck();
71+
72+
emit ContractUpgraded(oldImplementation, _getImplementation());
73+
}
74+
75+
function upgradeToAndCall(
76+
address newImplementation,
77+
bytes memory data
78+
) external payable override onlyProxy {
79+
address oldImplementation = _getImplementation();
80+
_authorizeUpgrade(newImplementation);
81+
_upgradeToAndCallUUPS(newImplementation, data, true);
82+
83+
magicCheck();
84+
85+
emit ContractUpgraded(oldImplementation, _getImplementation());
86+
}
87+
88+
function magicCheck() internal view {
89+
// Calling a method using `this.<method>` will cause a contract call that will use
90+
// the new contract. This call will fail if the method does not exists or the magic
91+
// is different.
92+
if (this.entropyUpgradableMagic() != 0x66697265)
93+
revert EntropyErrors.InvalidUpgradeMagic();
94+
}
95+
96+
function entropyUpgradableMagic() public pure returns (uint32) {
97+
return 0x66697265;
98+
}
99+
}

target_chains/ethereum/contracts/forge-test/Entropy.t.sol

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ pragma solidity ^0.8.0;
44

55
import "forge-std/Test.sol";
66
import "@pythnetwork/entropy-sdk-solidity/EntropyStructs.sol";
7-
import "../contracts/entropy/Entropy.sol";
7+
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
88
import "./utils/EntropyTestUtils.t.sol";
9+
import "../contracts/entropy/EntropyUpgradable.sol";
910

1011
// TODO
1112
// - fuzz test?
1213
contract EntropyTest is Test, EntropyTestUtils {
13-
Entropy public random;
14+
ERC1967Proxy public proxy;
15+
EntropyUpgradable public random;
1416

1517
uint128 pythFeeInWei = 7;
1618

@@ -33,8 +35,18 @@ contract EntropyTest is Test, EntropyTestUtils {
3335
uint128 MAX_UINT128 = 2 ** 128 - 1;
3436
bytes32 ALL_ZEROS = bytes32(uint256(0));
3537

38+
address public owner = address(8);
39+
address public admin = address(9);
40+
address public admin2 = address(10);
41+
3642
function setUp() public {
37-
random = new Entropy(pythFeeInWei, provider1, false);
43+
EntropyUpgradable _random = new EntropyUpgradable();
44+
// deploy proxy contract and point it to implementation
45+
proxy = new ERC1967Proxy(address(_random), "");
46+
// wrap in ABI to support easier calls
47+
random = EntropyUpgradable(address(proxy));
48+
49+
random.initialize(owner, admin, pythFeeInWei, provider1, false);
3850

3951
bytes32[] memory hashChain1 = generateHashChain(
4052
provider1,
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// SPDX-License-Identifier: Apache 2
2+
3+
pragma solidity ^0.8.0;
4+
5+
import "./utils/EntropyTestUtils.t.sol";
6+
import "../contracts/entropy/EntropyUpgradable.sol";
7+
import "./utils/EntropyTestContracts/EntropyDifferentMagic.t.sol";
8+
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
9+
import "@pythnetwork/entropy-sdk-solidity/EntropyErrors.sol";
10+
11+
contract EntropyAuthorized is Test, EntropyTestUtils {
12+
ERC1967Proxy public proxy;
13+
EntropyUpgradable public random;
14+
EntropyUpgradable public random2;
15+
EntropyDifferentMagic public randomDifferentMagic;
16+
17+
address public owner = address(1);
18+
address public admin = address(2);
19+
address public admin2 = address(3);
20+
21+
// We don't need to register providers for these tests
22+
// We are just checking for the default provider, which
23+
// only required an address.
24+
address public provider1 = address(4);
25+
address public provider2 = address(5);
26+
27+
uint128 pythFeeInWei = 7;
28+
29+
function setUp() public {
30+
EntropyUpgradable _random = new EntropyUpgradable();
31+
// deploy proxy contract and point it to implementation
32+
proxy = new ERC1967Proxy(address(_random), "");
33+
// wrap in ABI to support easier calls
34+
random = EntropyUpgradable(address(proxy));
35+
// to test for upgrade
36+
random2 = new EntropyUpgradable();
37+
randomDifferentMagic = new EntropyDifferentMagic();
38+
39+
random.initialize(owner, admin, pythFeeInWei, provider1, false);
40+
}
41+
42+
function testSetAdminByAdmin() public {
43+
vm.prank(admin);
44+
random.setAdmin(admin2);
45+
assertEq(random.getAdmin(), admin2);
46+
}
47+
48+
function testSetAdminByOwner() public {
49+
vm.prank(owner);
50+
random.setAdmin(admin2);
51+
assertEq(random.getAdmin(), admin2);
52+
}
53+
54+
function testExpectRevertSetAdminByUnauthorized() public {
55+
vm.expectRevert(EntropyErrors.Unauthorized.selector);
56+
vm.prank(admin2);
57+
random.setAdmin(admin);
58+
}
59+
60+
function testSetPythFeeByAdmin() public {
61+
vm.prank(admin);
62+
random.setPythFee(10);
63+
assertEq(random.getPythFee(), 10);
64+
}
65+
66+
function testSetPythFeeByOwner() public {
67+
vm.prank(owner);
68+
random.setPythFee(10);
69+
assertEq(random.getPythFee(), 10);
70+
}
71+
72+
function testExpectRevertSetPythFeeByUnauthorized() public {
73+
vm.expectRevert(EntropyErrors.Unauthorized.selector);
74+
vm.prank(admin2);
75+
random.setPythFee(10);
76+
}
77+
78+
function testSetDefaultProviderByOwner() public {
79+
vm.prank(owner);
80+
random.setDefaultProvider(provider2);
81+
assertEq(random.getDefaultProvider(), provider2);
82+
}
83+
84+
function testSetDefaultProviderByAdmin() public {
85+
vm.prank(admin);
86+
random.setDefaultProvider(provider2);
87+
assertEq(random.getDefaultProvider(), provider2);
88+
}
89+
90+
function testExpectRevertSetDefaultProviderByUnauthorized() public {
91+
vm.expectRevert(EntropyErrors.Unauthorized.selector);
92+
vm.prank(admin2);
93+
random.setDefaultProvider(provider2);
94+
}
95+
96+
function testUpgradeByOwner() public {
97+
vm.prank(owner);
98+
random.upgradeTo(address(random2));
99+
}
100+
101+
function testExpectRevertUpgradeByAdmin() public {
102+
// The message is returned by openzepplin upgrade contracts
103+
vm.expectRevert("Ownable: caller is not the owner");
104+
vm.prank(admin);
105+
random.upgradeTo(address(random2));
106+
}
107+
108+
function testExpectRevertUpgradeByUnauthorized() public {
109+
// The message is returned by openzepplin upgrade contracts
110+
vm.expectRevert("Ownable: caller is not the owner");
111+
vm.prank(provider1);
112+
random.upgradeTo(address(random2));
113+
}
114+
115+
// There can be another case that the magic function doesn't
116+
// exist but it's fine. (It will revert with no reason)
117+
// The randomDifferentMagic contract do have a magic in this case
118+
function testExpectRevertDifferentMagicContractUpgrade() public {
119+
vm.expectRevert(EntropyErrors.InvalidUpgradeMagic.selector);
120+
vm.prank(owner);
121+
random.upgradeTo(address(randomDifferentMagic));
122+
}
123+
}

0 commit comments

Comments
 (0)