Skip to content

Commit

Permalink
add deploy scripts, pass tests
Browse files Browse the repository at this point in the history
  • Loading branch information
piotrostr committed Sep 7, 2024
1 parent 6d3ad5f commit 70ea2cd
Show file tree
Hide file tree
Showing 6 changed files with 318 additions and 100 deletions.
96 changes: 44 additions & 52 deletions packages/foundry/contracts/FairDrop.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@ import { IWormholeRelayer } from
import { IWormholeReceiver } from
"wormhole-solidity-sdk/interfaces/IWormholeReceiver.sol";

// this could probably be broken into separate contracts for supporting
// solely Optimism verification and neater design
contract FairDrop {
mapping(address => bool) verified;
mapping(address => bool) public verified;

using ByteHasher for bytes;

error DuplicateNullifier(uint256 nullifierHash);
error ReceiverAlreadySet();

/// @dev The World ID instance that will be used for verifying proofs
IWorldID public immutable worldId;
Expand All @@ -26,7 +25,9 @@ contract FairDrop {
/// @dev The World ID group ID (always 1)
uint256 internal immutable groupId = 1;

/// @dev Whether a nullifier hash has been used already. Used to guarantee an action is only performed once by a single person
/// @dev Whether a nullifier hash has been used already. Used to guarantee an
//action is only performed once by a single person
/// nullifier hash <=> hash(person's identity) through $WLD ID
mapping(uint256 => bool) internal nullifierHashes;

/// @param nullifierHash The nullifier hash for the verified proof
Expand All @@ -36,6 +37,7 @@ contract FairDrop {
event SentForDelivery(
uint256 timestamp, uint256 deliveryQuote, uint256 payloadLength
);
event ReceiverSet(uint16 chainId, address receiverAddress);

uint16 public chainId;
uint16 public receiverChainId;
Expand All @@ -46,21 +48,12 @@ contract FairDrop {
// haven't messed with this param yet
uint256 constant GAS_LIMIT = 100_000;

/// @param _worldId The WorldID router that will verify the proofs
/// @param _appId The World ID app ID
/// @param _actionId The World ID action ID
// @param _wormhole The Wormhole contract address
// @param _chainId The chain ID of the FairDrop contract
// @param _receiverAddress The address of the receiver contract (foreign chain)
// @param _receiverChainId The chain ID of the receiver chain
constructor(
address _worldId,
string memory _appId,
string memory _actionId,
address _wormholeRelayer,
uint16 _chainId,
address _receiverAddress,
uint16 _receiverChainId
uint16 _chainId
) {
worldId = IWorldID(_worldId);
wormholeRelayer = IWormholeRelayer(_wormholeRelayer);
Expand All @@ -70,27 +63,36 @@ contract FairDrop {
).hashToField();

chainId = _chainId;
receiverAddress = _receiverAddress;
}

// @dev Set the receiver address, for now only one, this should be extended
// in the future
// my intuition is to create a registry contract that will store the
// addresses and allow for separate transactions whenever applicable
// so like ~8 mainnet addresses for 8 different chains, configurable
// in the frontend
function setReceiver(
uint16 _receiverChainId,
address _receiverAddress
) external {
if (receiverAddress != address(0)) revert ReceiverAlreadySet();
receiverChainId = _receiverChainId;
receiverAddress = _receiverAddress;
emit ReceiverSet(_receiverChainId, _receiverAddress);
}

/// @param signal An arbitrary input from the user, usually the user's wallet address (check README for further details)
/// @param root The root of the Merkle tree (returned by the JS widget).
/// @param nullifierHash The nullifier hash for this proof, preventing double signaling (returned by the JS widget).
/// @param proof The zero-knowledge proof that demonstrates the claimer is registered with World ID (returned by the JS widget).
/// @dev Feel free to rename this method however you want! We've used `claim`, `verify` or `execute` in the past.
function verifyAndPropagate(
address signal,
uint256 root,
uint256 nullifierHash,
uint256[8] calldata proof
) public {
// First, we make sure this person hasn't done this before
) public payable {
require(receiverAddress != address(0), "Receiver not set");

if (nullifierHashes[nullifierHash]) {
revert DuplicateNullifier(nullifierHash);
}

// We now verify the provided proof is valid and the user is verified by World ID
worldId.verifyProof(
root,
groupId,
Expand All @@ -100,38 +102,25 @@ contract FairDrop {
proof
);

// Update proof of uniqueness and mark the user as verified
nullifierHashes[nullifierHash] = true;
verified[msg.sender] = true;

emit Verified(nullifierHash);

// Propagate the verified signal to the receiver chain
// in production, it is highly advised there is some encryption mechanism
// possibly introduced here, it seems a bit insecure
// there is the verification of the VM, but if the proof is interceipted
// at the web2 level, it could be a front-run attack opportunity if man in
// the middle is there
bytes memory payload = abi.encodePacked(msg.sender, nullifierHash);
// Get a quote for the cost of gas for delivery
// receiverValue param is 0 since we are not sending any ether
bytes memory payload = abi.encode(msg.sender, nullifierHash);
uint256 deliveryQuote = getDeliveryQuote(0);

// Send the message
wormholeRelayer.sendPayloadToEvm{ value: deliveryQuote }(
receiverChainId,
receiverAddress,
abi.encode(payload),
payload,
0, // no receiverValue
GAS_LIMIT
);
// get timestamp here

emit SentForDelivery(block.timestamp, deliveryQuote, payload.length);
}

/// @param receiverValue The value to send to the receiver contract
/// @dev public view method for the frontend to get the get the quote
/// before constructing the tx
function getDeliveryQuote(
uint256 receiverValue
) public view returns (uint256 cost) {
Expand All @@ -148,27 +137,35 @@ contract FairDrop {
}

contract FairDropSatellite is IWormholeReceiver {
IWormholeReceiver public immutable wormholeReceiver;
error VerifierAlreadySet();

uint16 public chainId;
uint16 public verifierChainId;
address public verifierAddress;
uint16 public optimismChainId = 10;
uint16 public constant OPTIMISM_CHAIN_ID = 10;

mapping(address => bool) public verifiedUsers;
mapping(uint256 => bool) public usedNullifiers;

event VerificationPropagated(
uint256 timestamp, address user, uint256 nullifierHash
);
event VerifierSet(uint16 chainId, address verifierAddress);

constructor(
uint16 _chainId,
uint16 _verifierChainId,
address _verifierAddress
uint16 _chainId
) {
chainId = _chainId;
}

function setVerifier(
uint16 _verifierChainId,
address _verifierAddress
) external {
if (verifierAddress != address(0)) revert VerifierAlreadySet();
verifierChainId = _verifierChainId;
verifierAddress = _verifierAddress;
emit VerifierSet(_verifierChainId, _verifierAddress);
}

function receiveWormholeMessages(
Expand All @@ -178,32 +175,27 @@ contract FairDropSatellite is IWormholeReceiver {
uint16 sourceChain,
bytes32 // deliveryHash
) external payable {
require(verifierAddress != address(0), "Verifier not set");
address parsedVerifierAddress = address(uint160(uint256(sourceAddress)));
require(
parsedVerifierAddress == verifierAddress, "Invalid verifier address"
);
require(
sourceChain == optimismChainId,
sourceChain == OPTIMISM_CHAIN_ID,
"Invalid verifier chain ID, need Optimism chain ID"
);

// haven't checked this yet
// bytes32 verifierDeliveryHash =
// keccak256(abi.encodePacked(payload, sourceAddress, sourceChain));
// require(verifierDeliveryHash == deliveryHash, "Invalid delivery hash");

(address user, uint256 nullifierHash) =
abi.decode(payload, (address, uint256));

require(!usedNullifiers[nullifierHash], "Nullifier already used");
require(!usedNullifiers[nullifierHash], "Sybil Alert!");

verifiedUsers[user] = true;
usedNullifiers[nullifierHash] = true;

emit VerificationPropagated(block.timestamp, user, nullifierHash);
}

// Function to check if a user is verified
function isVerified(
address user
) public view returns (bool) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,29 @@ import "../contracts/FairDrop.sol";
import "./DeployHelpers.s.sol";

contract DeployFairDrop is ScaffoldETHDeploy {
// use `deployer` from `ScaffoldETHDeploy`
function run() external ScaffoldEthDeployerRunner {
// WORLD_ID ENS optimism.id.worldcoin.eth
console.log("deployer: ", msg.sender);
require(block.chainid == 10, "Must be deployed on Optimism");

// WorldID address on Optimism
address worldId = 0x57f928158C3EE7CDad1e4D8642503c4D0201f611;
string memory appId = "app_9f3c3c467dbfb7c616671b1b07c3f221";
string memory actionId = "verify";
FairDrop fairDrop = new FairDrop(IWorldID(worldId), appId, actionId);

// Wormhole Relayer address on Optimism (same for all EVM chains)
address wormholeRelayer = 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911;

// Optimism chain ID
uint16 chainId = 10;

FairDrop fairDrop =
new FairDrop(worldId, appId, actionId, wormholeRelayer, chainId);

console.logString(
string.concat("FairDrop deployed at: ", vm.toString(address(fairDrop)))
);

// Add deployment to the list
deployments.push(Deployment("FairDrop", address(fairDrop)));
}
}
28 changes: 28 additions & 0 deletions packages/foundry/script/01_deploy_satellite.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
pragma solidity ^0.8.19;

import "../contracts/FairDrop.sol";
import "./DeployHelpers.s.sol";

contract DeployFairDropSatellite is ScaffoldETHDeploy {
function run() external ScaffoldEthDeployerRunner {
// Check if we're on Arbitrum
require(block.chainid == 42161, "Must be deployed on Arbitrum");

console.log("deployer: ", msg.sender);

uint16 chainId = uint16(block.chainid);

FairDropSatellite fairDropSatellite = new FairDropSatellite(chainId);

console.logString(
string.concat(
"FairDropSatellite deployed at: ",
vm.toString(address(fairDropSatellite))
)
);

deployments.push(
Deployment("FairDropSatellite", address(fairDropSatellite))
);
}
}
29 changes: 29 additions & 0 deletions packages/foundry/script/01_deploy_satetllite.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "../contracts/FairDrop.sol";
import "./DeployHelpers.s.sol";

contract DeployFairDropSatellite is ScaffoldETHDeploy {
function run() external ScaffoldEthDeployerRunner {
// Check if we're on Arbitrum
require(block.chainid == 42161, "Must be deployed on Arbitrum");

console.log("deployer: ", msg.sender);

uint16 chainId = uint16(block.chainid);

FairDropSatellite fairDropSatellite = new FairDropSatellite(chainId);

console.logString(
string.concat(
"FairDropSatellite deployed at: ",
vm.toString(address(fairDropSatellite))
)
);

deployments.push(
Deployment("FairDropSatellite", address(fairDropSatellite))
);
}
}
6 changes: 3 additions & 3 deletions packages/foundry/script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ pragma solidity ^0.8.19;

import "../contracts/FairDrop.sol";
import "./DeployHelpers.s.sol";
import { DeployFairDrop } from "./00_deploy_your_contract.s.sol";
import { DeployFairDrop } from "./00_deploy_fair_drop.s.sol";

contract DeployScript is ScaffoldETHDeploy {
function run() external {
DeployFairDrop deployFairDrop = new DeployFairDrop();
deployFairDrop.run();
// DeployFairDrop deployFairDrop = new DeployFairDrop();
// deployFairDrop.run();

// deploy more contracts here
// DeployMyContract deployMyContract = new DeployMyContract();
Expand Down
Loading

0 comments on commit 70ea2cd

Please sign in to comment.