Skip to content

Commit

Permalink
Add token (#1604)
Browse files Browse the repository at this point in the history
  • Loading branch information
jrchatruc authored Dec 11, 2024
1 parent 3094722 commit ee2efe2
Show file tree
Hide file tree
Showing 13 changed files with 431 additions and 0 deletions.
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
1 change: 1 addition & 0 deletions claim_contracts/lib/openzeppelin-contracts-upgradeable
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

0 comments on commit ee2efe2

Please sign in to comment.