Skip to content

Commit f0c67c5

Browse files
author
Dev Kalra
authored
[entropy] audit: 3. two step transfer (#1178)
* two step owner transfer * admin two step transfer * remove redundant test
1 parent 941ee77 commit f0c67c5

File tree

4 files changed

+107
-32
lines changed

4 files changed

+107
-32
lines changed

target_chains/ethereum/contracts/contracts/entropy/EntropyGovernance.sol

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,49 @@ import "./EntropyState.sol";
99
* @dev `Governance` defines a means to enacting changes to the Entropy contract.
1010
*/
1111
abstract contract EntropyGovernance is EntropyState {
12-
event AdminSet(address oldAdmin, address newAdmin);
1312
event PythFeeSet(uint oldPythFee, uint newPythFee);
1413
event DefaultProviderSet(
1514
address oldDefaultProvider,
1615
address newDefaultProvider
1716
);
1817

19-
function getAdmin() external view returns (address) {
20-
return _state.admin;
18+
event NewAdminProposed(address oldAdmin, address newAdmin);
19+
event NewAdminAccepted(address oldAdmin, address newAdmin);
20+
21+
/**
22+
* @dev Returns the address of the proposed admin.
23+
*/
24+
function proposedAdmin() public view virtual returns (address) {
25+
return _state.proposedAdmin;
2126
}
2227

2328
/**
24-
* @dev Set the admin of the contract.
25-
*
26-
* Calls {_authoriseAdminAction}.
27-
*
28-
* Emits an {AdminSet} event.
29+
* @dev Proposes a new admin of the contract. Replaces the proposed admin if there is one.
30+
* Can only be called by either admin or owner.
2931
*/
30-
function setAdmin(address newAdmin) external {
32+
function proposeAdmin(address newAdmin) public virtual {
3133
_authoriseAdminAction();
3234

35+
_state.proposedAdmin = newAdmin;
36+
emit NewAdminProposed(_state.admin, newAdmin);
37+
}
38+
39+
/**
40+
* @dev The proposed admin accepts the admin transfer.
41+
*/
42+
function acceptAdmin() external {
43+
if (msg.sender != _state.proposedAdmin)
44+
revert EntropyErrors.Unauthorized();
45+
3346
address oldAdmin = _state.admin;
34-
_state.admin = newAdmin;
47+
_state.admin = msg.sender;
3548

36-
emit AdminSet(oldAdmin, newAdmin);
49+
_state.proposedAdmin = address(0);
50+
emit NewAdminAccepted(oldAdmin, msg.sender);
51+
}
52+
53+
function getAdmin() external view returns (address) {
54+
return _state.admin;
3755
}
3856

3957
/**

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ contract EntropyInternalStructs {
3434
mapping(bytes32 => EntropyStructs.Request) requestsOverflow;
3535
// Mapping from randomness providers to information about each them.
3636
mapping(address => EntropyStructs.ProviderInfo) providers;
37+
// proposedAdmin is the new admin's account address proposed by either the owner or the current admin.
38+
// If there is no pending transfer request, this value will hold `address(0)`.
39+
address proposedAdmin;
3740
}
3841
}
3942

target_chains/ethereum/contracts/contracts/entropy/EntropyUpgradable.sol

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

44
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
55
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
6-
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
6+
import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
77
import "@pythnetwork/entropy-sdk-solidity/EntropyErrors.sol";
88

99
import "./EntropyGovernance.sol";
1010
import "./Entropy.sol";
1111

1212
contract EntropyUpgradable is
1313
Initializable,
14-
OwnableUpgradeable,
14+
Ownable2StepUpgradeable,
1515
UUPSUpgradeable,
1616
Entropy,
1717
EntropyGovernance
@@ -42,7 +42,7 @@ contract EntropyUpgradable is
4242
);
4343

4444
// We need to transfer the ownership from deployer to the new owner
45-
transferOwnership(owner);
45+
_transferOwnership(owner);
4646
}
4747

4848
/// Ensures the contract cannot be uninitialized and taken over.

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

Lines changed: 72 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ contract EntropyAuthorized is Test, EntropyTestUtils {
2424
address public provider1 = address(4);
2525
address public provider2 = address(5);
2626

27+
address public owner2 = address(6);
28+
2729
uint128 pythFeeInWei = 7;
2830

2931
function setUp() public {
@@ -39,24 +41,6 @@ contract EntropyAuthorized is Test, EntropyTestUtils {
3941
random.initialize(owner, admin, pythFeeInWei, provider1, false);
4042
}
4143

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-
6044
function testSetPythFeeByAdmin() public {
6145
vm.prank(admin);
6246
random.setPythFee(10);
@@ -120,4 +104,74 @@ contract EntropyAuthorized is Test, EntropyTestUtils {
120104
vm.prank(owner);
121105
random.upgradeTo(address(randomDifferentMagic));
122106
}
107+
108+
function testExpectRevertRequestOwnershipTransferByUnauthorized() public {
109+
vm.expectRevert("Ownable: caller is not the owner");
110+
vm.prank(provider1);
111+
random.transferOwnership(owner2);
112+
}
113+
114+
function testExpectRevertRequestOwnershipTransferByAdmin() public {
115+
vm.expectRevert("Ownable: caller is not the owner");
116+
vm.prank(admin);
117+
random.transferOwnership(owner2);
118+
}
119+
120+
function testRequestAndAcceptOwnershipTransfer() public {
121+
vm.prank(owner);
122+
random.transferOwnership(owner2);
123+
assertEq(random.pendingOwner(), owner2);
124+
125+
vm.prank(owner2);
126+
random.acceptOwnership();
127+
assertEq(random.owner(), owner2);
128+
}
129+
130+
function testRequestAndAcceptOwnershipTransferUnauthorizedAccept() public {
131+
vm.prank(owner);
132+
random.transferOwnership(owner2);
133+
assertEq(random.pendingOwner(), owner2);
134+
135+
vm.prank(admin);
136+
vm.expectRevert("Ownable2Step: caller is not the new owner");
137+
random.acceptOwnership();
138+
}
139+
140+
function testProposeAdminByOwner() public {
141+
vm.prank(owner);
142+
random.proposeAdmin(admin2);
143+
144+
assertEq(random.proposedAdmin(), admin2);
145+
}
146+
147+
function testProposeAdminByAdmin() public {
148+
vm.prank(admin);
149+
random.proposeAdmin(admin2);
150+
151+
assertEq(random.proposedAdmin(), admin2);
152+
}
153+
154+
function testProposeAdminByUnauthorized() public {
155+
vm.expectRevert(EntropyErrors.Unauthorized.selector);
156+
random.proposeAdmin(admin2);
157+
}
158+
159+
function testAcceptAdminByPropsed() public {
160+
vm.prank(owner);
161+
random.proposeAdmin(admin2);
162+
163+
vm.prank(admin2);
164+
random.acceptAdmin();
165+
166+
assertEq(random.getAdmin(), admin2);
167+
}
168+
169+
function testAcceptAdminByUnauthorized() public {
170+
vm.prank(owner);
171+
random.proposeAdmin(admin2);
172+
173+
vm.prank(provider1);
174+
vm.expectRevert(EntropyErrors.Unauthorized.selector);
175+
random.acceptAdmin();
176+
}
123177
}

0 commit comments

Comments
 (0)