Skip to content

Commit

Permalink
✨ make stadium address changeable (#43)
Browse files Browse the repository at this point in the history
* ✨ make stadium address changeable

* 🔨 fix script for avalanche

* ✅ add tests for constructor's StadiumAddress

* 💡 change comment

* 🐛 add zero check in `setStadiumAddress`
  • Loading branch information
thurendous authored Oct 3, 2023
1 parent ce7c3ad commit cd5ccc2
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 42 deletions.
6 changes: 5 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
PRIVATE_KEY=
POLYSCAN_API_KEY=
SEPOLIA_RPC_URL=
AVALANCHE_RPC_URL=
AVALANCHE_RPC_URL=

# used in deployContracts.s.sol
STADIUM_ADDRESS=0x1FBcd7D20155274DFD796343149D0FCA41338F14
SPARKN_DEV=
52 changes: 36 additions & 16 deletions script/DeployContracts.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,44 @@ import {HelperConfig} from "./HelperConfig.s.sol";
contract DeployContracts is Script {
// tokens' array to whitelist
// satadium_address = 0x5aB0ffF1a51ee78F67247ac0B90C8c1f1f54c37F
address[] finalTokensToWhitelist;
address public stadiumAddress = 0x1FBcd7D20155274DFD796343149D0FCA41338F14; // SPARKN STADDIUM
address public factoryAdmin = 0xbe5b0d1386BE331080fbb2C8c517BAA148497D97; // SPARKN DEV
address[] public finalTokensToWhitelist;
address public stadiumAddress = vm.envAddress("STADIUM_ADDRESS"); // SPARKN STADDIUM
address public factoryAdmin = vm.envAddress("SPARKN_DEV"); // SPARKN DEV
address public jpycv1Address;
address public jpycv2Address;
address public usdcAddress;
address public usdtAddress;
uint256 public deployerKey;

function run() external returns (ProxyFactory, Distributor, HelperConfig) {
// set up config
HelperConfig config = new HelperConfig();
// get the addresses of the tokens to whitelist
(address jpycv1Address, address jpycv2Address, address usdcAddress,, uint256 deployerKey) =
config.activeNetworkConfig();
address[] memory tokensToWhitelist = new address[](3);
// whitelist 3 kinds of tokens
tokensToWhitelist[0] = jpycv1Address;
tokensToWhitelist[1] = jpycv2Address;
tokensToWhitelist[2] = usdcAddress;
for (uint256 i; i < 3; ++i) {
if (tokensToWhitelist[i] != address(0)) {
finalTokensToWhitelist.push(tokensToWhitelist[i]);
if (block.chainid == 43114) {
// get the addresses of the tokens to whitelist
(,jpycv2Address, usdcAddress,, deployerKey) =
config.activeNetworkConfig();
address[] memory tokensToWhitelist = new address[](2);
// whitelist 3 kinds of tokens
tokensToWhitelist[0] = jpycv2Address;
tokensToWhitelist[1] = usdcAddress;
for (uint256 i; i < tokensToWhitelist.length; ++i) {
if (tokensToWhitelist[i] != address(0)) {
finalTokensToWhitelist.push(tokensToWhitelist[i]);
}
}
} else {
// get the addresses of the tokens to whitelist
(jpycv1Address, jpycv2Address, usdcAddress,, deployerKey) =
config.activeNetworkConfig();
address[] memory tokensToWhitelist = new address[](3);
// whitelist 3 kinds of tokens
tokensToWhitelist[0] = jpycv1Address;
tokensToWhitelist[1] = jpycv2Address;
tokensToWhitelist[2] = usdcAddress;
for (uint256 i; i < tokensToWhitelist.length; ++i) {
if (tokensToWhitelist[i] != address(0)) {
finalTokensToWhitelist.push(tokensToWhitelist[i]);
}
}
}

Expand All @@ -41,7 +61,7 @@ contract DeployContracts is Script {

vm.startBroadcast(deployerKey); // prank
// console.log("Deploying contracts...sender: ", msg.sender);
ProxyFactory proxyFactory = new ProxyFactory(finalTokensToWhitelist);
ProxyFactory proxyFactory = new ProxyFactory(finalTokensToWhitelist, stadiumAddress);
// console.log("proxyFactory Owner: %s", proxyFactory.owner());
// console.log("address this: %s", address(this));
// console.log("address deployerKey: %s", deployerKey);
Expand All @@ -51,7 +71,7 @@ contract DeployContracts is Script {

// deploy distributor - implementation contract
// 5% as starting fee
Distributor distributor = new Distributor(address(proxyFactory), stadiumAddress);
Distributor distributor = new Distributor(address(proxyFactory));
// no need to deploy proxies in the beginning
// Proxy proxyA = proxyFactory.deployProxy(address(distributor));
// Proxy proxyB = proxyFactory.deployProxy(address(distributor));
Expand Down
2 changes: 1 addition & 1 deletion script/HelperConfig.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ contract HelperConfig is Script {
jpycv1Address: address(0),
jpycv2Address: 0x431D5dfF03120AFA4bDf332c61A6e1766eF37BDB,
usdcAddress: 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E,
usdtAddress: 0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7,
usdtAddress: address(0),
deployerKey: vm.envUint("PRIVATE_KEY")
});
}
Expand Down
25 changes: 15 additions & 10 deletions src/Distributor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ contract Distributor {
/* solhint-disable */
uint8 private constant VERSION = 1; // version is 1 for now
address private immutable FACTORY_ADDRESS;
address private immutable STADIUM_ADDRESS;
uint256 private constant COMMISSION_FEE = 500; // this can be changed in the future
// a constant value of 10,000 (basis points) = 100%
uint256 private constant BASIS_POINTS = 10_000;
Expand All @@ -68,14 +67,12 @@ contract Distributor {
/// @dev initiate the contract with factory address and other key addresses, fee rate
constructor(
// uint256 version, // for future use
address factoryAddress,
address stadiumAddress
address factoryAddress
)
/* solhint-enable */
{
if (factoryAddress == address(0) || stadiumAddress == address(0)) revert Distributor__NoZeroAddress();
if (factoryAddress == address(0)) revert Distributor__NoZeroAddress();
FACTORY_ADDRESS = factoryAddress; // initialize with deployed factory address beforehand
STADIUM_ADDRESS = stadiumAddress; // official address to receive commission fee
}

////////////////////////////////////////////
Expand Down Expand Up @@ -106,7 +103,7 @@ contract Distributor {
* The token address must be one of the whitelisted tokens
* The winners and percentages array are supposed not to be so long, so the loop can stay unbounded
* The total percentage must be correct. It must be (100 - COMMITION_FEE).
* Finally send the remained token(fee) to STADIUM_ADDRESS with no dust in the contract
* Finally send the remained token(fee) to proxyFactory's stadiumAddress with no dust in the contract
* @param token The token address
* @param winners The addresses of winners
* @param percentages The percentages of winners
Expand Down Expand Up @@ -149,18 +146,18 @@ contract Distributor {
revert Distributor__MismatchedPercentages();
}

// send commission fee as well as all the remaining tokens to STADIUM_ADDRESS to avoid dust remaining
// send commission fee as well as all the remaining tokens to stadiumAddress to avoid dust remaining
_commissionTransfer(erc20);
emit Distributed(token, winners, percentages, data);
}

/**
* @notice Transfer commission fee to STADIUM_ADDRESS
* @notice Transfer commission fee to stadiumAddress
* @dev This internal function is called after distribution in `_distribute` function
* @param token The token address
*/
function _commissionTransfer(IERC20 token) internal {
token.safeTransfer(STADIUM_ADDRESS, token.balanceOf(address(this)));
token.safeTransfer(getStadiumAddress(), token.balanceOf(address(this)));
}

/**
Expand All @@ -186,9 +183,17 @@ contract Distributor {
{
/* solhint-disable */
_FACTORY_ADDRESS = FACTORY_ADDRESS;
_STADIUM_ADDRESS = STADIUM_ADDRESS;
_STADIUM_ADDRESS = getStadiumAddress();
_COMMISSION_FEE = COMMISSION_FEE;
_VERSION = VERSION;
/* solhint-enable */
}

/**
* @notice returns stadium address from proxy factory
* @dev This function is for convenience to get the stadium address
*/
function getStadiumAddress() internal view returns (address) {
return ProxyFactory(FACTORY_ADDRESS).stadiumAddress();
}
}
17 changes: 16 additions & 1 deletion src/ProxyFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ contract ProxyFactory is Ownable, EIP712 {
event SetContest(
address indexed organizer, bytes32 indexed contestId, uint256 closeTime, address indexed implementation
);
event SetStadiumAddress(address indexed stadiumAddress);
event Distributed(address indexed proxy, bytes data);

////////////////////////////////
Expand All @@ -70,6 +71,7 @@ contract ProxyFactory is Ownable, EIP712 {
keccak256("DeployAndDistribute(bytes32 contestId,address implementation,bytes data)");
uint256 public constant EXPIRATION_TIME = 7 days;
uint256 public constant MAX_CONTEST_PERIOD = 28 days;
address public stadiumAddress; // official address to receive commission fee

/// @notice record contest close time by salt
/// @dev The contest doesn't exist when value is 0
Expand All @@ -85,15 +87,17 @@ contract ProxyFactory is Ownable, EIP712 {
* @notice the array is not supposed to be so long because only major tokens will get listed
* @param _whitelistedTokens The tokens array to get whitelisted
*/
constructor(address[] memory _whitelistedTokens) EIP712("ProxyFactory", "1") Ownable() {
constructor(address[] memory _whitelistedTokens, address _stadiumAddress) EIP712("ProxyFactory", "1") Ownable() {
if (_whitelistedTokens.length == 0) revert ProxyFactory__NoEmptyArray();
if (_stadiumAddress == address(0)) revert ProxyFactory__NoZeroAddress();
for (uint256 i; i < _whitelistedTokens.length;) {
if (_whitelistedTokens[i] == address(0)) revert ProxyFactory__NoZeroAddress();
whitelistedTokens[_whitelistedTokens[i]] = true;
unchecked {
++i;
}
}
stadiumAddress = _stadiumAddress;
}

////////////////////////////////////////////
Expand Down Expand Up @@ -235,6 +239,17 @@ contract ProxyFactory is Ownable, EIP712 {
proxy = address(uint160(uint256(hash)));
}

/**
* @notice Only owner can set stadium address
* @dev Set stadium address
* @param newStadiumAddress The new stadium address
*/
function setStadiumAddress(address newStadiumAddress) public onlyOwner {
if (newStadiumAddress == address(0)) revert ProxyFactory__NoZeroAddress();
stadiumAddress = newStadiumAddress;
emit SetStadiumAddress(newStadiumAddress);
}

///////////////////////////////////
/////// Internal functions ////////
///////////////////////////////////
Expand Down
1 change: 1 addition & 0 deletions test/integration/HelperContract.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ abstract contract HelperContract is Test {
ProxyFactory public proxyFactory;
Proxy public proxy;
Distributor public distributor;
address[] public finalTokensToWhitelist;

// distributor instance through proxy address
Distributor public proxyWithDistributorLogic;
Expand Down
59 changes: 55 additions & 4 deletions test/integration/ProxyFactoryTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ contract ProxyFactoryTest is StdCheats, HelperContract {
address[] memory tokensToWhitelist = new address[](0);
// should revert
vm.expectRevert(ProxyFactory.ProxyFactory__NoEmptyArray.selector);
new ProxyFactory(tokensToWhitelist);
new ProxyFactory(tokensToWhitelist, stadiumAddress);
}

function testConstructorWhitelistedTokensWithZeroAddressesThenRevert() public {
Expand All @@ -125,7 +125,7 @@ contract ProxyFactoryTest is StdCheats, HelperContract {
address[] memory tokensToWhitelist = new address[](2);
// should revert
vm.expectRevert(ProxyFactory.ProxyFactory__NoZeroAddress.selector);
new ProxyFactory(tokensToWhitelist);
new ProxyFactory(tokensToWhitelist, stadiumAddress);
}

function testConstructorVariablesAreSetCorrectly() public {
Expand All @@ -136,7 +136,7 @@ contract ProxyFactoryTest is StdCheats, HelperContract {
tokensToWhitelist[1] = jpycv2Address;
tokensToWhitelist[2] = usdcAddress;
// deploy contracts
ProxyFactory newProxyFactory = new ProxyFactory(tokensToWhitelist);
ProxyFactory newProxyFactory = new ProxyFactory(tokensToWhitelist, stadiumAddress);
// check whitelist
assertTrue(newProxyFactory.whitelistedTokens(jpycv1Address));
assertTrue(newProxyFactory.whitelistedTokens(jpycv2Address));
Expand Down Expand Up @@ -1189,7 +1189,7 @@ contract ProxyFactoryTest is StdCheats, HelperContract {
//
// 4. For some reason there is a new distributor implementation.
// The Owner set the new distributor for the same contestId
Distributor newDistributor = new Distributor(address(proxyFactory), stadiumAddress);
Distributor newDistributor = new Distributor(address(proxyFactory));
vm.startPrank(factoryAdmin);
proxyFactory.setContest(organizer, contestId, block.timestamp + 8 days, address(newDistributor));
vm.stopPrank();
Expand Down Expand Up @@ -1389,10 +1389,61 @@ contract ProxyFactoryTest is StdCheats, HelperContract {
proxyFactory.distributeByOwner(organizer, randomId, address(distributor), dataToSendToAdmin);
}

///////////////////////////
///// getProxyAddress /////
///////////////////////////

function testGetProxyAddressRevertsIfDoesntExist() public {
bytes32 randomId = keccak256(abi.encode("NotRegistered", "000"));
bytes32 salt = keccak256(abi.encode(organizer, randomId, address(distributor)));
vm.expectRevert(ProxyFactory.ProxyFactory__ContestIsNotRegistered.selector);
proxyFactory.getProxyAddress(salt, address(distributor));
}

//////////////////////////
///// stadiumAddress /////
//////////////////////////

function testCanGetStadiumAddress() public {
address stadiumAddressGot = proxyFactory.stadiumAddress();
assertEq(stadiumAddressGot, stadiumAddress);
}

function testOwnerCanSetStadiumAddress() public {
address newStadiumAddress = address(0x123);
vm.prank(factoryAdmin);
proxyFactory.setStadiumAddress(newStadiumAddress);
address stadiumAddressGot = proxyFactory.stadiumAddress();
assertEq(stadiumAddressGot, newStadiumAddress);
}

function testNonownerCannotSetStadiumAddress() public {
address newStadiumAddress = address(0x123);
address newStadiumAddress2 = makeAddr("NEW_STADIUM_ADDRESS");
vm.prank(organizer);
vm.expectRevert("Ownable: caller is not the owner");
proxyFactory.setStadiumAddress(newStadiumAddress);
vm.prank(supporter);
vm.expectRevert("Ownable: caller is not the owner");
proxyFactory.setStadiumAddress(newStadiumAddress2);
address stadiumAddressGot = proxyFactory.stadiumAddress();
assertEq(stadiumAddressGot, stadiumAddress);
assertFalse(stadiumAddressGot == newStadiumAddress);
}

function testIfStadiumAddressIsSetAsZeroInConstructorThenRevert() public {
address zeroStadiumAddress = address(0x0);
(, jpycv2Address, usdcAddress,,) = config.activeNetworkConfig();
address[] memory tokensToWhitelist = new address[](2);
// whitelist 3 kinds of tokens
tokensToWhitelist[0] = jpycv2Address;
tokensToWhitelist[1] = usdcAddress;
for (uint256 i; i < tokensToWhitelist.length; ++i) {
if (tokensToWhitelist[i] != address(0)) {
finalTokensToWhitelist.push(tokensToWhitelist[i]);
}
}
vm.expectRevert(ProxyFactory.ProxyFactory__NoZeroAddress.selector);
new ProxyFactory(finalTokensToWhitelist, zeroStadiumAddress);
}
}
Loading

0 comments on commit cd5ccc2

Please sign in to comment.