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

smart contracts #2

Merged
merged 1 commit into from
Jul 13, 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
127 changes: 127 additions & 0 deletions packages/hardhat/contracts/BoxManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract BoxManager is Ownable {

uint256 public DELIVERY_REPORT_DEADLINE = 60 * 60 * 24 * 4;
uint256 public nextBoxId;
IERC20 public token;

mapping(uint256 => Box) public boxes;
mapping(uint256 => BoxConfiguration) public boxConfig;
mapping(uint256 => mapping(address => uint256)) public courierStakes;
mapping(address => uint256) public courierRewards;
mapping(address => bool) public courierBlacklist;

enum BoxStatus {
EMPTY,
FULL,
DELIVERY,
DELIVERED
}

struct BoxConfiguration {
uint256 stakeMin;
uint256 stakeMax;
uint256 bountyMin;
uint256 bountyMax;
}

struct Box {
address operator;
address sender;
address receiver;
address courier;
uint256 bounty;
uint256 stake;
uint256 pickupTime;
string location;
BoxStatus status;
}

constructor(address initialOwner, IERC20 _token) Ownable(initialOwner) {
token = _token;
}

function registerBox(uint256 stakeMin, uint256 stakeMax, uint256 bountyMin, uint256 bountyMax) public {
require(stakeMin < stakeMax && bountyMin <= bountyMax, "Invalid stake or bounty amount");
boxes[nextBoxId] = Box(msg.sender, address(0x0), address(0x0), address(0x0), 0, 0, 0, "", BoxStatus.EMPTY);
boxConfig[nextBoxId] = BoxConfiguration(stakeMin, stakeMax, bountyMin, bountyMax);
nextBoxId++;
}

function sendPackage(uint256 boxId, string memory location, address receiver, uint256 requiredStake, uint256 bountyAmount) public payable {
require(isBoxRegistered(boxId), "Box not registered");
require(requiredStake >= boxConfig[boxId].stakeMin && requiredStake <= boxConfig[boxId].stakeMax, "Stake amount outside allowed range");
require(bountyAmount >= boxConfig[boxId].bountyMin && bountyAmount <= boxConfig[boxId].bountyMax, "Bounty amount outside allowed range");
// courierStakes[boxId][msg.sender] = requiredStake;
boxes[boxId].sender = msg.sender;
boxes[boxId].receiver = receiver;
boxes[boxId].bounty = bountyAmount;
boxes[boxId].stake = requiredStake;
boxes[boxId].location = location;
boxes[boxId].status = BoxStatus.FULL;

token.transferFrom(msg.sender, address(this), bountyAmount);

}

function stake(uint256 boxId) public payable {
require(isBoxRegistered(boxId), "Box not registered");
// change bs to static state value
require(msg.value >= boxConfig[boxId].stakeMin && msg.value <= boxConfig[boxId].stakeMax, "Stake amount outside allowed range");
// courierStakes[boxId][msg.sender] = msg.value;
require(courierBlacklist[msg.sender] == false, "You are blacklisted from deliveries");
// @ToDo do the worldcoin check
boxes[boxId].courier = msg.sender;
boxes[boxId].pickupTime = block.timestamp;
boxes[boxId].status = BoxStatus.DELIVERY;

token.transferFrom(msg.sender, address(this), boxes[boxId].stake);
}

function confirmDelivery(uint256 boxId) public {
require(isBoxRegistered(boxId), "Box not registered");
require(msg.sender == boxes[boxId].receiver, "Only receiver can confirm delivery");
require(boxes[boxId].status == BoxStatus.DELIVERY, "Incorrect box status");

courierStakes[boxId][msg.sender] += boxes[boxId].stake;
courierRewards[boxes[boxId].courier] += boxes[boxId].bounty;
boxes[boxId].status = BoxStatus.EMPTY;

// @ToDo nullify the box state
}

function reportDelivery(uint256 boxId) public {
require(isBoxRegistered(boxId), "Box not registered");
require(boxes[boxId].status == BoxStatus.DELIVERY, "Incorrect box status");
require(boxes[boxId].pickupTime >= DELIVERY_REPORT_DEADLINE, "Can't report delivery before 4 days have passed");
require(msg.sender == boxes[boxId].receiver, "Only receiver can report delivery");

token.transfer(boxes[boxId].sender, boxes[boxId].stake + boxes[boxId].bounty);
boxes[boxId].status = BoxStatus.EMPTY;
courierBlacklist[boxes[boxId].courier] = true;

}

function unstake(uint256 boxId) public {
require(isBoxRegistered(boxId), "Box not registered");
require(courierStakes[boxId][msg.sender] > 0, "No stake found for courier");
// require(boxes[boxId].status == BoxStatus.DELIVERY, "Incorrect box status");
token.transfer(msg.sender, courierStakes[boxId][msg.sender]);
courierStakes[boxId][msg.sender] = 0;
}

function withdrawRewards() public {
require(courierRewards[msg.sender] > 0, "No rewards found for courier");
token.transfer(msg.sender, courierRewards[msg.sender]);
courierRewards[msg.sender] = 0;
}

function isBoxRegistered(uint256 boxId) public view returns (bool) {
return boxes[boxId].operator != address(0);
}
}
18 changes: 18 additions & 0 deletions packages/hardhat/contracts/BoxToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";

contract BoxToken is ERC20, Ownable, ERC20Permit {
constructor(address initialOwner)
ERC20("BoxToken", "BOX")
Ownable(initialOwner)
ERC20Permit("BoxToken")
{}

function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}
68 changes: 68 additions & 0 deletions packages/hardhat/contracts/WorldcoinIdentity.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import { ByteHasher } from './helpers/ByteHasher.sol';
import { IWorldID } from './interfaces/IWorldID.sol';

contract Contract {
using ByteHasher for bytes;

///////////////////////////////////////////////////////////////////////////////
/// ERRORS ///
//////////////////////////////////////////////////////////////////////////////

/// @notice Thrown when attempting to reuse a nullifier
error DuplicateNullifier(uint256 nullifierHash);

/// @dev The World ID instance that will be used for verifying proofs
IWorldID internal immutable worldId;

/// @dev The contract's external nullifier hash
uint256 internal immutable externalNullifier;

/// @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
mapping(uint256 => bool) internal nullifierHashes;

/// @param nullifierHash The nullifier hash for the verified proof
/// @dev A placeholder event that is emitted when a user successfully verifies with World ID
event Verified(uint256 nullifierHash);

/// @param _worldId The WorldID router that will verify the proofs
/// @param _appId The World ID app ID
/// @param _actionId The World ID action ID
constructor(IWorldID _worldId, string memory _appId, string memory _actionId) {
worldId = _worldId;
externalNullifier = abi.encodePacked(abi.encodePacked(_appId).hashToField(), _actionId).hashToField();
}

/// @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 verifyAndExecute(address signal, uint256 root, uint256 nullifierHash, uint256[8] calldata proof) public {
// First, we make sure this person hasn't done this before
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,
abi.encodePacked(signal).hashToField(),
nullifierHash,
externalNullifier,
proof
);

// We now record the user has done this, so they can't do it again (proof of uniqueness)
nullifierHashes[nullifierHash] = true;

// Finally, execute your logic here, for example issue a token, NFT, etc...
// Make sure to emit some kind of event afterwards!

emit Verified(nullifierHash);
}
}
12 changes: 12 additions & 0 deletions packages/hardhat/contracts/helpers/ByteHasher.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

library ByteHasher {
/// @dev Creates a keccak256 hash of a bytestring.
/// @param value The bytestring to hash
/// @return The hash of the specified value
/// @dev `>> 8` makes sure that the result is included in our field
function hashToField(bytes memory value) internal pure returns (uint256) {
return uint256(keccak256(abi.encodePacked(value))) >> 8;
}
}
21 changes: 21 additions & 0 deletions packages/hardhat/contracts/interfaces/IWorldID.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

interface IWorldID {
/// @notice Reverts if the zero-knowledge proof is invalid.
/// @param root The of the Merkle tree
/// @param groupId The id of the Semaphore group
/// @param signalHash A keccak256 hash of the Semaphore signal
/// @param nullifierHash The nullifier hash
/// @param externalNullifierHash A keccak256 hash of the external nullifier
/// @param proof The zero-knowledge proof
/// @dev Note that a double-signaling check is not included here, and should be carried by the caller.
function verifyProof(
uint256 root,
uint256 groupId,
uint256 signalHash,
uint256 nullifierHash,
uint256 externalNullifierHash,
uint256[8] calldata proof
) external view;
}
14 changes: 14 additions & 0 deletions packages/hardhat/deploy/00_deploy_your_contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEn
autoMine: true,
});

await deploy("BoxToken", {
from: deployer,
args: [deployer],
log: true,
autoMine: true,
});

await deploy("BoxManager", {
from: deployer,
args: [deployer],
log: true,
autoMine: true,
});

// Get the deployed contract to interact with it after deploying.
const yourContract = await hre.ethers.getContract<Contract>("YourContract", deployer);
console.log("👋 Initial greeting:", await yourContract.greeting());
Expand Down
Loading