Skip to content

Commit

Permalink
ballotNames now include all provided proposal arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
othernet-global committed Feb 18, 2024
1 parent dff0437 commit 39921b4
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 118 deletions.
21 changes: 9 additions & 12 deletions src/dao/DAO.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import "../Upkeep.sol";
contract DAO is IDAO, Parameters, ReentrancyGuard
{
event ParameterBallotFinalized(uint256 indexed ballotID, Vote winningVote);
event SetContract(string indexed ballotName, address indexed contractAddress);
event SetAccessManager(address indexed contractAddress);
event SetWebsiteURL(string newURL);
event WhitelistToken(IERC20 indexed token);
event UnwhitelistToken(IERC20 indexed token);
Expand Down Expand Up @@ -112,14 +112,11 @@ contract DAO is IDAO, Parameters, ReentrancyGuard
}


function _executeSetContract( Ballot memory ballot ) internal
function _executeSetAccessManager( Ballot memory ballot ) internal
{
bytes32 nameHash = keccak256(bytes( ballot.ballotName ) );
exchangeConfig.setAccessManager( IAccessManager(ballot.address1) );

if ( nameHash == keccak256(bytes( "confirm_setContract:accessManager" )) )
exchangeConfig.setAccessManager( IAccessManager(ballot.address1) );

emit SetContract(ballot.ballotName, ballot.address1);
emit SetAccessManager(ballot.address1);
}


Expand Down Expand Up @@ -177,16 +174,16 @@ contract DAO is IDAO, Parameters, ReentrancyGuard
emit GeoExclusionUpdated(ballot.string1, true, exchangeConfig.accessManager().geoVersion());
}

// Once an initial setContract proposal passes, it automatically starts a second confirmation ballot (to prevent last minute approvals)
else if ( ballot.ballotType == BallotType.SET_CONTRACT )
proposals.createConfirmationProposal( string.concat("confirm_", ballot.ballotName), BallotType.CONFIRM_SET_CONTRACT, ballot.address1, "", ballot.description );
// Once an initial setAccessManager proposal passes, it automatically starts a second confirmation ballot (to prevent last minute approvals)
else if ( ballot.ballotType == BallotType.SET_ACCESS_MANAGER )
proposals.createConfirmationProposal( string.concat("confirm_", ballot.ballotName), BallotType.CONFIRM_SET_ACCESS_MANAGER, ballot.address1, "", ballot.description );

// Once an initial setWebsiteURL proposal passes, it automatically starts a second confirmation ballot (to prevent last minute approvals)
else if ( ballot.ballotType == BallotType.SET_WEBSITE_URL )
proposals.createConfirmationProposal( string.concat("confirm_", ballot.ballotName), BallotType.CONFIRM_SET_WEBSITE_URL, address(0), ballot.string1, ballot.description );

else if ( ballot.ballotType == BallotType.CONFIRM_SET_CONTRACT )
_executeSetContract( ballot );
else if ( ballot.ballotType == BallotType.CONFIRM_SET_ACCESS_MANAGER )
_executeSetAccessManager( ballot );

else if ( ballot.ballotType == BallotType.CONFIRM_SET_WEBSITE_URL )
_executeSetWebsiteURL( ballot );
Expand Down
22 changes: 11 additions & 11 deletions src/dao/Proposals.sol
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ contract Proposals is IProposals, ReentrancyGuard

function proposeParameterBallot( uint256 parameterType, string calldata description ) external nonReentrant returns (uint256 ballotID)
{
string memory ballotName = string.concat("parameter:", Strings.toString(parameterType) );
string memory ballotName = string.concat("parameter:", Strings.toString(parameterType), description );
return _possiblyCreateProposal( ballotName, BallotType.PARAMETER, address(0), parameterType, "", description );
}

Expand All @@ -169,7 +169,7 @@ contract Proposals is IProposals, ReentrancyGuard
require( poolsConfig.numberOfWhitelistedPools() < poolsConfig.maximumWhitelistedPools(), "Maximum number of whitelisted pools already reached" );
require( ! poolsConfig.tokenHasBeenWhitelisted(token, exchangeConfig.wbtc(), exchangeConfig.weth()), "The token has already been whitelisted" );

string memory ballotName = string.concat("whitelist:", Strings.toHexString(address(token)) );
string memory ballotName = string.concat("whitelist:", Strings.toHexString(address(token)), tokenIconURL, description );

uint256 ballotID = _possiblyCreateProposal( ballotName, BallotType.WHITELIST_TOKEN, address(token), 0, tokenIconURL, description );
_openBallotsForTokenWhitelisting.add( ballotID );
Expand All @@ -186,7 +186,7 @@ contract Proposals is IProposals, ReentrancyGuard
require( address(token) != address(exchangeConfig.usdc()), "Cannot unwhitelist USDC" );
require( address(token) != address(exchangeConfig.salt()), "Cannot unwhitelist SALT" );

string memory ballotName = string.concat("unwhitelist:", Strings.toHexString(address(token)) );
string memory ballotName = string.concat("unwhitelist:", Strings.toHexString(address(token)), tokenIconURL, description );
return _possiblyCreateProposal( ballotName, BallotType.UNWHITELIST_TOKEN, address(token), 0, tokenIconURL, description );
}

Expand All @@ -204,7 +204,7 @@ contract Proposals is IProposals, ReentrancyGuard

// This ballotName is not unique for the receiving wallet and enforces the restriction of one sendSALT ballot at a time.
// If more receivers are necessary at once, a splitter can be used.
string memory ballotName = "sendSALT";
string memory ballotName = string.concat("sendSALT:", Strings.toHexString(wallet), Strings.toString(amount), description );
return _possiblyCreateProposal( ballotName, BallotType.SEND_SALT, wallet, amount, "", description );
}

Expand All @@ -214,7 +214,7 @@ contract Proposals is IProposals, ReentrancyGuard
{
require( contractAddress != address(0), "Contract address cannot be address(0)" );

string memory ballotName = string.concat("callContract:", Strings.toHexString(address(contractAddress)) );
string memory ballotName = string.concat("callContract:", Strings.toHexString(address(contractAddress)), Strings.toString(number), description );
return _possiblyCreateProposal( ballotName, BallotType.CALL_CONTRACT, contractAddress, number, description, "" );
}

Expand All @@ -223,7 +223,7 @@ contract Proposals is IProposals, ReentrancyGuard
{
require( bytes(country).length == 2, "Country must be an ISO 3166 Alpha-2 Code" );

string memory ballotName = string.concat("include:", country );
string memory ballotName = string.concat("include:", country, description );
return _possiblyCreateProposal( ballotName, BallotType.INCLUDE_COUNTRY, address(0), 0, country, description );
}

Expand All @@ -232,25 +232,25 @@ contract Proposals is IProposals, ReentrancyGuard
{
require( bytes(country).length == 2, "Country must be an ISO 3166 Alpha-2 Code" );

string memory ballotName = string.concat("exclude:", country );
string memory ballotName = string.concat("exclude:", country, description );
return _possiblyCreateProposal( ballotName, BallotType.EXCLUDE_COUNTRY, address(0), 0, country, description );
}


function proposeSetContractAddress( string calldata contractName, address newAddress, string calldata description ) external nonReentrant returns (uint256 ballotID)
function proposeSetAccessManager( address newAddress, string calldata description ) external nonReentrant returns (uint256 ballotID)
{
require( newAddress != address(0), "Proposed address cannot be address(0)" );

string memory ballotName = string.concat("setContract:", contractName );
return _possiblyCreateProposal( ballotName, BallotType.SET_CONTRACT, newAddress, 0, "", description );
string memory ballotName = string.concat("setAccessManager:", Strings.toHexString(newAddress), description );
return _possiblyCreateProposal( ballotName, BallotType.SET_ACCESS_MANAGER, newAddress, 0, "", description );
}


function proposeWebsiteUpdate( string calldata newWebsiteURL, string calldata description ) external nonReentrant returns (uint256 ballotID)
{
require( keccak256(abi.encodePacked(newWebsiteURL)) != keccak256(abi.encodePacked("")), "newWebsiteURL cannot be empty" );

string memory ballotName = string.concat("setURL:", newWebsiteURL );
string memory ballotName = string.concat("setURL:", newWebsiteURL, description );
return _possiblyCreateProposal( ballotName, BallotType.SET_WEBSITE_URL, address(0), 0, newWebsiteURL, description );
}

Expand Down
4 changes: 2 additions & 2 deletions src/dao/interfaces/IProposals.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";


enum Vote { INCREASE, DECREASE, NO_CHANGE, YES, NO }
enum BallotType { PARAMETER, WHITELIST_TOKEN, UNWHITELIST_TOKEN, SEND_SALT, CALL_CONTRACT, INCLUDE_COUNTRY, EXCLUDE_COUNTRY, SET_CONTRACT, SET_WEBSITE_URL, CONFIRM_SET_CONTRACT, CONFIRM_SET_WEBSITE_URL }
enum BallotType { PARAMETER, WHITELIST_TOKEN, UNWHITELIST_TOKEN, SEND_SALT, CALL_CONTRACT, INCLUDE_COUNTRY, EXCLUDE_COUNTRY, SET_ACCESS_MANAGER, SET_WEBSITE_URL, CONFIRM_SET_ACCESS_MANAGER, CONFIRM_SET_WEBSITE_URL }

struct UserVote
{
Expand Down Expand Up @@ -45,7 +45,7 @@ interface IProposals
function proposeCallContract( address contractAddress, uint256 number, string calldata description ) external returns (uint256 ballotID);
function proposeCountryInclusion( string calldata country, string calldata description ) external returns (uint256 ballotID);
function proposeCountryExclusion( string calldata country, string calldata description ) external returns (uint256 ballotID);
function proposeSetContractAddress( string calldata contractName, address newAddress, string calldata description ) external returns (uint256 ballotID);
function proposeSetAccessManager( address newAddress, string calldata description ) external returns (uint256 ballotID);
function proposeWebsiteUpdate( string calldata newWebsiteURL, string calldata description ) external returns (uint256 ballotID);

function castVote( uint256 ballotID, Vote vote ) external;
Expand Down
55 changes: 14 additions & 41 deletions src/dao/tests/DAO.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -473,35 +473,19 @@ contract TestDAO is Deployment
}


function _contractForName( string memory contractName ) internal view returns (address)
{
bytes32 nameHash = keccak256(bytes(contractName));

if ( nameHash == keccak256(bytes("accessManager" )))
return address(exchangeConfig.accessManager());
if ( nameHash == keccak256(bytes("priceFeed1" )))
return address(priceAggregator.priceFeed1());
if ( nameHash == keccak256(bytes("priceFeed2" )))
return address(priceAggregator.priceFeed2());
if ( nameHash == keccak256(bytes("priceFeed3" )))
return address(priceAggregator.priceFeed3());

return address(0);
}


function _checkSetContractApproved( uint256 ballotID, string memory contractName, address newAddress) internal
function _checkAccessManagerApproved( uint256 ballotID, address newAddress) internal
{
vm.startPrank(alice);
staking.stakeSALT( 1000000 ether );

proposals.proposeSetContractAddress( contractName, newAddress, "description" );
proposals.proposeSetAccessManager( newAddress, "description" );
_voteForAndFinalizeBallot(ballotID, Vote.YES);

// Above finalization should create a confirmation ballot
_voteForAndFinalizeBallot(ballotID + 1, Vote.YES);

assertEq( _contractForName(contractName), newAddress, "Contract address should have changed" );
assertEq( address(exchangeConfig.accessManager()), newAddress, "AccessManager should have changed" );
vm.stopPrank();
}

Expand All @@ -510,61 +494,50 @@ contract TestDAO is Deployment
function testSetContractApproved() public
{
// Done last to prevent access issues
_checkSetContractApproved( 1, "accessManager", address( new AccessManager(dao) ) );
_checkAccessManagerApproved( 1, address( new AccessManager(dao) ) );
}


function _checkSetContractDenied1( uint256 ballotID, string memory contractName, address newAddress) internal
function _checkSetAccessManagerDenied1( uint256 ballotID, address newAddress) internal
{
vm.startPrank(alice);
staking.stakeSALT( 1000000 ether );

proposals.proposeSetContractAddress( contractName, newAddress, "description" );
proposals.proposeSetAccessManager( newAddress, "description" );
_voteForAndFinalizeBallot(ballotID, Vote.NO);

assertFalse( _contractForName(contractName) == newAddress, "Contract address should not have changed" );
assertFalse( address(exchangeConfig.accessManager()) == newAddress, "Contract address should not have changed" );
vm.stopPrank();
}


// A unit test to test that with all possible contract options, finalizing a setContract ballot has no effect when the initial ballot fails
function testSetContractDenied1() public
{
_checkSetContractDenied1( 1, "priceFeed", address(0x1231231 ) );
_checkSetContractDenied1( 2, "accessManager", address( new AccessManager(dao) ) );
_checkSetContractDenied1( 3, "stakingRewardsEmitter", address(0x1231233 ) );
_checkSetContractDenied1( 4, "liquidityRewardsEmitter", address(0x1231234 ) );
_checkSetContractDenied1( 5, "priceFeed1", address(0x1231236 ) );
_checkSetContractDenied1( 6, "priceFeed2", address(0x1231237 ) );
_checkSetContractDenied1( 7, "priceFeed3", address(0x1231238 ) );
_checkSetAccessManagerDenied1( 1, address( new AccessManager(dao) ) );
}


function _checkSetContractDenied2( uint256 ballotID, string memory contractName, address newAddress) internal
function _checkSetContractDenied2( uint256 ballotID, address newAddress) internal
{
vm.startPrank(alice);
staking.stakeSALT( 1000000 ether );

proposals.proposeSetContractAddress( contractName, newAddress, "description" );
proposals.proposeSetAccessManager(newAddress, "description" );
_voteForAndFinalizeBallot(ballotID, Vote.YES);

// Above finalization should create a confirmation ballot
_voteForAndFinalizeBallot(ballotID + 1, Vote.NO);

assertFalse( _contractForName(contractName) == newAddress, "Contract address should not have changed" );
assertFalse( address(exchangeConfig.accessManager()) == newAddress, "Contract address should not have changed" );
vm.stopPrank();
}


// A unit test to test that with all possible contract options, finalizing a setContract ballot has no effect when the confirm ballot fails
function testSetContractDenied2() public
{
_checkSetContractDenied2( 1, "accessManager", address( new AccessManager(dao) ) );
_checkSetContractDenied2( 3, "stakingRewardsEmitter", address(0x1231233 ) );
_checkSetContractDenied2( 5, "liquidityRewardsEmitter", address(0x1231234 ) );
_checkSetContractDenied2( 7, "priceFeed1", address(0x1231236 ) );
_checkSetContractDenied2( 9, "priceFeed2", address(0x1231237 ) );
_checkSetContractDenied2( 11, "priceFeed3", address(0x1231238 ) );
_checkSetContractDenied2( 1, address( new AccessManager(dao) ) );
}


Expand Down Expand Up @@ -807,7 +780,7 @@ contract TestDAO is Deployment
vm.stopPrank();

// Retrieve the initial ballot ID
uint256 initialBallotID = proposals.openBallotsByName("parameter:0");
uint256 initialBallotID = proposals.openBallotsByName("parameter:0test");
assertEq(initialBallotID, 1, "Initial ballot ID should be 1");

// Warp time to allow for another proposal
Expand All @@ -819,7 +792,7 @@ contract TestDAO is Deployment
vm.stopPrank();

// Retrieve the second ballot ID
uint256 secondBallotID = proposals.openBallotsByName("parameter:2");
uint256 secondBallotID = proposals.openBallotsByName("parameter:2test");
assertEq(secondBallotID, initialBallotID + 1, "Second ballot ID should increment by 1");
}

Expand Down
Loading

0 comments on commit 39921b4

Please sign in to comment.