Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add token #1604

Merged
merged 5 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,12 @@
[submodule "contracts/lib/openzeppelin-contracts-upgradeable"]
path = contracts/lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
[submodule "claim_contracts/lib/forge-std"]
path = claim_contracts/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "claim_contracts/lib/openzeppelin-contracts-upgradeable"]
path = claim_contracts/lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
[submodule "claim_contracts/lib/openzeppelin-contracts"]
path = claim_contracts/lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
14 changes: 14 additions & 0 deletions claim_contracts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Compiler files
cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env
36 changes: 36 additions & 0 deletions claim_contracts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
## AlignedToken

## Requirements

- Foundry

## Local deploying

To deploy the contracts, set the following environment variables:

- `DEPLOYER_PRIVATE_KEY`: The private key of the account that's going to deploy the contracts.
- `SAFE_ADDRESS`: The address of the safe that's going to own the Proxy admin that in turn owns the token and airdrop contracts.
- `OWNER1_ADDRESS`, `OWNER2_ADDRESS`, and `OWNER3_ADDRESS`: The three owners of the token.
- `MINT_AMOUNT`: The amount to mint to each account (the contract actually supports minting different amounts of the token to each owner, but in the deploy script we simplified it).
- `RPC_URL`: The url of the network to deploy to.
- `CLAIM_TIME_LIMIT`: The claim time limit timestamp.
- `MERKLE_ROOT`: The merkle root of all valid token claims.

Example:
```
export DEPLOYER_PRIVATE_KEY=<deployer_private_key>
export SAFE_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
export OWNER1_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
export OWNER2_ADDRESS=0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC
export OWNER3_ADDRESS=0x90F79bf6EB2c4f870365E785982E1f101E93b906
export MINT_AMOUNT=100
export RPC_URL=http://localhost:8545
export CLAIM_TIME_LIMIT=2733247661
export MERKLE_ROOT=0x90076b5fb9a6c81d9fce83dfd51760987b8c49e7c861ea25b328e6e63d2cd3df
```

Then run the following script:

```
./deployClaim.sh
```
18 changes: 18 additions & 0 deletions claim_contracts/deployClaim.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash

forge --version >/dev/null 2>&1
if [ $? != 0 ]; then
echo "Error: Please make sure you have forge installed and in your PATH"
exit 2
fi

safe=${SAFE_ADDRESS:-$1}
owner1=${OWNER1_ADDRESS:-$2}
owner2=${OWNER2_ADDRESS:-$3}
owner3=${OWNER3_ADDRESS:-$4}
mint_amount=${MINT_AMOUNT:-$5}
rpc_url=${RPC_URL:-$6}
claim_time_limit=${CLAIM_TIME_LIMIT:-2733247661}
merkle_root=${MERKLE_ROOT:-$7}

cd script && forge script DeployScript $safe $owner1 $owner2 $owner3 $mint_amount $claim_time_limit $merkle_root --sig "run(address,address,address,address,uint256,uint256,bytes32)" --fork-url $rpc_url --broadcast
6 changes: 6 additions & 0 deletions claim_contracts/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[profile.default]
src = "src"
out = "out"
libs = ["lib"]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
1 change: 1 addition & 0 deletions claim_contracts/lib/forge-std
Submodule forge-std added at 1eea5b
1 change: 1 addition & 0 deletions claim_contracts/lib/openzeppelin-contracts
Submodule openzeppelin-contracts added at 69c8de
2 changes: 2 additions & 0 deletions claim_contracts/remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/
@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/
72 changes: 72 additions & 0 deletions claim_contracts/script/Utils.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {Vm} from "forge-std/Vm.sol";

library Utils {
// Cheatcodes address, 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D.
address internal constant VM_ADDRESS =
address(uint160(uint256(keccak256("hevm cheat code"))));
Vm internal constant vm = Vm(VM_ADDRESS);

/// @notice Address of the deterministic create2 factory.
/// @dev This address corresponds to a contracts that is set in the storage
/// in the genesis file. The same contract with the same address is deployed
/// in every testnet, so if this script is run in a testnet instead of in a
/// local environment, it should work.
address constant DETERMINISTIC_CREATE2_ADDRESS =
0x4e59b44847b379578588920cA78FbF26c0B4956C;

function deployWithCreate2(
bytes memory bytecode,
bytes32 salt,
address create2Factory,
uint256 signerPrivateKey
) internal returns (address) {
if (bytecode.length == 0) {
revert("Bytecode is not set");
}
address contractAddress = vm.computeCreate2Address(
salt,
keccak256(bytecode),
create2Factory
);
if (contractAddress.code.length != 0) {
revert("Contract already deployed");
}

vm.broadcast(signerPrivateKey);
(bool success, bytes memory data) = create2Factory.call(
abi.encodePacked(salt, bytecode)
);
contractAddress = bytesToAddress(data);

if (!success) {
revert(
"Failed to deploy contract via create2: create2Factory call failed"
);
}

if (contractAddress == address(0)) {
revert(
"Failed to deploy contract via create2: contract address is zero"
);
}

if (contractAddress.code.length == 0) {
revert(
"Failed to deploy contract via create2: contract code is empty"
);
}

return contractAddress;
}

function bytesToAddress(
bytes memory addressOffset
) internal pure returns (address addr) {
assembly {
addr := mload(add(addressOffset, 20))
}
}
}
175 changes: 175 additions & 0 deletions claim_contracts/script/deploy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Script, console} from "forge-std/Script.sol";
import {AlignedToken} from "../src/AlignedToken.sol";
import {ClaimableAirdrop} from "../src/ClaimableAirdrop.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import {Utils} from "./Utils.sol";

contract DeployScript is Script {
struct Supply {
address beneficiary;
uint256 amount;
}

function setUp() public {}

function run(
address safe,
address owner1,
address owner2,
address owner3,
uint256 mintAmount,
uint256 limitTimestamp,
bytes32 merkleRoot
) public {
console.log("Deploying contracts");
uint256 deployer = vm.envUint("DEPLOYER_PRIVATE_KEY");

ProxyAdmin contractsProxyAdmin = deployProxyAdmin(safe, deployer);

TransparentUpgradeableProxy tokenContractProxy = deployTokenContractProxy(
address(contractsProxyAdmin),
owner1,
owner2,
owner3,
mintAmount,
deployer
);

TransparentUpgradeableProxy airdropContractProxy = deployAirdropContractProxy(
address(contractsProxyAdmin),
address(tokenContractProxy),
owner3,
limitTimestamp,
merkleRoot,
deployer
);

vm.startBroadcast();
(deployer);
(bool success, bytes memory data) = address(tokenContractProxy).call(
abi.encodeCall(
IERC20.approve,
(address(airdropContractProxy), mintAmount)
)
);
bool approved;
assembly {
approved := mload(add(data, 0x20))
}

if (!success || !approved) {
revert("Failed to give approval to airdrop contract");
}
vm.stopBroadcast();

console.log("Succesfully gave approval to airdrop contract");
}

function deployProxyAdmin(
address tokenContractProxyAdminOwner,
uint256 signerPrivateKey
) internal returns (ProxyAdmin) {
bytes memory bytecode = abi.encodePacked(
type(ProxyAdmin).creationCode,
abi.encode(tokenContractProxyAdminOwner)
);
bytes32 salt = bytes32(0);
address proxyAdminAddress = Utils.deployWithCreate2(
bytecode,
salt,
Utils.DETERMINISTIC_CREATE2_ADDRESS,
signerPrivateKey
);
console.log(
"Aligned Proxy Admin deployed at:",
proxyAdminAddress,
"with owner:",
tokenContractProxyAdminOwner
);
return ProxyAdmin(proxyAdminAddress);
}

function deployTokenContractProxy(
address tokenContractProxyAdmin,
address owner1,
address owner2,
address owner3,
uint256 mintAmount,
uint256 signerPrivateKey
) internal returns (TransparentUpgradeableProxy) {
vm.broadcast(signerPrivateKey);
AlignedToken tokenContract = new AlignedToken();
bytes memory bytecode = abi.encodePacked(
type(TransparentUpgradeableProxy).creationCode,
abi.encode(
address(tokenContract),
tokenContractProxyAdmin,
abi.encodeCall(
tokenContract.initialize,
(owner1, mintAmount, owner2, mintAmount, owner3, mintAmount)
)
)
);
bytes32 salt = bytes32(0);
address tokenContractProxyAddress = Utils.deployWithCreate2(
bytecode,
salt,
Utils.DETERMINISTIC_CREATE2_ADDRESS,
signerPrivateKey
);
console.log(
"Aligned Token Proxy deployed at:",
tokenContractProxyAddress,
"with admin:",
tokenContractProxyAdmin
);
return TransparentUpgradeableProxy(payable(tokenContractProxyAddress));
}

function deployAirdropContractProxy(
address airdropContractProxyAdmin,
address tokenContractProxyAddress,
address tokenOwnerAddress,
uint256 limitTimestamp,
bytes32 merkleRoot,
uint256 signerPrivateKey
) internal returns (TransparentUpgradeableProxy) {
vm.broadcast(signerPrivateKey);
ClaimableAirdrop airdropContract = new ClaimableAirdrop();
bytes memory bytecode = abi.encodePacked(
type(TransparentUpgradeableProxy).creationCode,
abi.encode(
address(airdropContract),
airdropContractProxyAdmin,
abi.encodeCall(
airdropContract.initialize,
(
tokenContractProxyAddress,
tokenOwnerAddress,
limitTimestamp,
merkleRoot
)
)
)
);
bytes32 salt = bytes32(0);
address airdropContractProxy = Utils.deployWithCreate2(
bytecode,
salt,
Utils.DETERMINISTIC_CREATE2_ADDRESS,
signerPrivateKey
);
console.log(
"Airdrop Proxy deployed at:",
airdropContractProxy,
"with admin:",
airdropContractProxyAdmin
);
return TransparentUpgradeableProxy(payable(airdropContractProxy));
}
}
33 changes: 33 additions & 0 deletions claim_contracts/src/AlignedToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract AlignedToken is Initializable, ERC20Upgradeable, ReentrancyGuard {
struct Supply {
address beneficiary;
uint256 amount;
}

constructor() {
// Ensure that initialization methods are run only once.
// This is a safeguard against accidental reinitialization.
_disableInitializers();
}

function initialize(
address beneficiary1,
uint256 amount1,
address beneficiary2,
uint256 amount2,
address beneficiary3,
uint256 amount3
) public initializer nonReentrant {
__ERC20_init("AlignedToken", "ALI");
_mint(beneficiary1, amount1);
_mint(beneficiary2, amount2);
_mint(beneficiary3, amount3);
}
}
Loading