Skip to content
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
58 changes: 56 additions & 2 deletions contracts/VenueMint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,28 @@ struct Ids {
bool exists;
}

struct Transferable {
bool transferable;
bool exists;
}

contract VenueMint is ERC1155Holder, ERC1155 {
address private owner; // Deployer of contract (us)
address private self; // The address of the contract (self)

mapping (string => address payable) private event_to_vendor; // Mapping event descriptions to vendor wallets
mapping (uint256 => uint256) private ticket_costs; // Mapping nft ids to cost
mapping (uint256 => uint256) private original_ticket_costs; // mapping nft ids to their original costs (ticket_costs gets zero'd on purchase)
mapping (string => Ids) private event_to_ids; // Mapping event descriptions to NFT ids
mapping (uint256 => Transferable) private id_to_transferable; // Mapping ticket id to whether they are allowed to be transferred to another user

uint256 last_id = 0; // The last id that we minted

Event[] private events;

event Event_Commencement(address indexed from, string description, string venue_URI, uint256 capacity);
event Buy_Ticket_Event(string description, uint256 count);
event User_To_User_Transfer_Concluded(address indexed seller, address indexed buyer);

// Set the owner to the deployer and self to the address of the contract
constructor() ERC1155("https://onlytickets.co/api/tokens/{id}.json") {
Expand Down Expand Up @@ -71,10 +79,15 @@ contract VenueMint is ERC1155Holder, ERC1155 {
amounts[i - last_id] = 1;
if (i < unique_seats) {
ticket_costs[i] = costs[i - last_id];
original_ticket_costs[i] = costs[i - last_id];
} else {
ticket_costs[i] = costs[costs.length - 1];
original_ticket_costs[i] = costs[costs.length - 1];
}

id_to_transferable[i].exists = true;


/*
if(i < unique_seats) {
amounts[i] = 1;
Expand Down Expand Up @@ -119,7 +132,7 @@ contract VenueMint is ERC1155Holder, ERC1155 {
}

// returns a list of all valid NFT ids for the event
function get_event_ids(string calldata description) public view returns (uint256[] memory) {
function get_event_ids(string calldata description) public view returns (uint256[] memory, Ids memory) {
Ids memory tmp = event_to_ids[description];
uint256 count = 0;

Expand All @@ -143,9 +156,10 @@ contract VenueMint is ERC1155Holder, ERC1155 {
}
}

return result;
return (result, tmp);
}

// returns true if the description is available. false otherwise
function is_description_available(string calldata description) public view returns (bool) {
return !event_to_ids[description].exists;
}
Expand Down Expand Up @@ -185,6 +199,46 @@ contract VenueMint is ERC1155Holder, ERC1155 {
return (true, total_cost);
}

// this enables the input user to transfer the callers tickets
function allow_user_to_user_ticket_transfer(uint256 ticketid) public returns (bool) {
Transferable memory tmp = id_to_transferable[ticketid];
require(tmp.exists, "The ticket id provided is not valid.");

id_to_transferable[ticketid].transferable = true;

setApprovalForAll(self, true);
return true;
}

// disables the contracts control of the senders tokens
function disallow_user_to_user_ticket_transfer() public {
setApprovalForAll(self, false);
}

// this should be called buy the purchaser of the ticket
function buy_ticket_from_user(address user, uint256 ticketid) payable public returns (bool) {
Transferable memory tmp = id_to_transferable[ticketid];

// we need to make sure this is a valid transfer
require(tmp.exists, "The ticket id provided is not valid.");
require(isApprovedForAll(user, self), "The seller has not authorized a transfer.");
require(tmp.transferable, "This ticket id provided is not transferable.");
require(msg.value >= original_ticket_costs[ticketid],"You did not send enough money to purchase the ticket.");

// send the money to the user
(bool success, ) = user.call{value:original_ticket_costs[ticketid]}("");
require(success, "Failed to pay the user you are purchasing from.");

// transfer the nft to the purchaser
_safeTransferFrom(user, msg.sender, ticketid, 1, "");

id_to_transferable[ticketid].transferable = false;

emit User_To_User_Transfer_Concluded(user, msg.sender);

return true;
}

function supportsInterface(bytes4 interfaceId) public view virtual override(ERC1155, ERC1155Holder)
returns (bool) {
return super.supportsInterface(interfaceId);
Expand Down
64 changes: 61 additions & 3 deletions test/VenueMint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { contracts } from "../typechain-types";
import { expect } from "chai";
import hre, { ethers } from "hardhat";
import { bigint } from "hardhat/internal/core/params/argumentTypes";
import { utils } from "../typechain-types/@openzeppelin/contracts";

// overall testing framework
describe("VenueMint", function () {
Expand Down Expand Up @@ -118,7 +119,7 @@ describe("VenueMint", function () {
const tmp = await contract.create_new_event("test", "0xblahblah", 5, 0, [5,5,5,5,5]);

// check that it returns properly for a valid and non valid event
expect(await contract.get_event_ids("test")).to.eql(Array(0n,1n,2n,3n,4n));
expect((await contract.get_event_ids("test"))[0]).to.eql(Array(0n,1n,2n,3n,4n));
expect(contract.get_event_ids("")).to.rejectedWith(Error);
})

Expand All @@ -133,7 +134,7 @@ describe("VenueMint", function () {
const user_contract_instance = contract.connect(user);
await user_contract_instance.buy_tickets("test", [0], {value: ethers.parseEther("1")});

expect(await contract.get_event_ids("test")).to.eql(Array(1n,2n,3n,4n))
expect((await contract.get_event_ids("test"))[0]).to.eql(Array(1n,2n,3n,4n))
})

it("will validate the description", async () => {
Expand All @@ -148,6 +149,63 @@ describe("VenueMint", function () {
expect(await contract.is_description_available("test")).to.equal(false);
})

describe("User to User Ticket Transfer", function () {
it("properly transfers tickets from user to user", async () => {
const init_contract = await loadFixture(deployOne);

// make wallets
const nftholderwallet = ethers.Wallet.createRandom().connect(ethers.provider);
const nftholderaddress = await nftholderwallet.getAddress();
const nftpurchaserwallet = ethers.Wallet.createRandom().connect(ethers.provider);
const nftpurchaseraddress = await nftpurchaserwallet.getAddress();

// give both money
ethers.provider.send("hardhat_setBalance", [nftholderaddress, "0xFFFFFFFFFFFFFFFFFFFFF"]);
ethers.provider.send("hardhat_setBalance", [nftpurchaseraddress, "0xFFFFFFFFFFFFFFFFFFFFF"]);

// make the holder instance and create the event
const holder_instance = init_contract.connect(nftholderwallet);
const tmp = await holder_instance.create_new_event("test", "cool beans", 1, 0, [2000]);

// buy the tickets and give the contract permission to control the holders tokens
const resp = await holder_instance.buy_tickets("test", [0], {value: ethers.parseEther("1")});
const resp1 = await holder_instance.allow_user_to_user_ticket_transfer(0);

// variable to check that we removed the contracts ability to control our tokens
// a filter that allows us to only execute the function based on the holder and purchaser address
var disallow_ran = false;
const holderfilter = init_contract.filters.User_To_User_Transfer_Concluded(nftholderaddress, nftpurchaseraddress);

// this is here purely for example (it is not necessary for our tests)
// setup a listener that removes the contracts ability to manage the holders tokens
holder_instance.on(holderfilter, async (response) => {
await holder_instance.disallow_user_to_user_ticket_transfer();
disallow_ran = true;
});

// create the purchaser instance
const purchaser_instance = holder_instance.connect(nftpurchaserwallet);

const holder_balance_before = await ethers.provider.getBalance(nftholderaddress);

// make sure that the ticket buying function emits the correct events
// make sure that the ticket was properly transfered from the two wallets
expect(await purchaser_instance.buy_ticket_from_user(nftholderaddress,0, {value: ethers.parseEther("1")})).to.emit(purchaser_instance, "User_To_User_Transfer_Concluded").withArgs(nftholderaddress, nftpurchaseraddress);
expect(await purchaser_instance.balanceOf(nftpurchaseraddress, 0)).to.equal(1n);

// make sure the seller got the correct amount of money
const holder_balance_after = await ethers.provider.getBalance(nftholderaddress);
expect(holder_balance_after - holder_balance_before).to.equal(2000);

// wait 5 seconds this is required for this to work right
// explanation here: https://stackoverflow.com/questions/68432609/contract-event-listener-is-not-firing-when-running-hardhat-tests-with-ethers-js
await new Promise(res => setTimeout(() => res(null), 5000));

// make sure the listener caught the event
expect(disallow_ran).to.equal(true);
})
})

describe("Vendor Payment Functionality", function () {

// checks that the vendor recieves the funds from one transaction
Expand Down Expand Up @@ -467,7 +525,7 @@ describe("VenueMint", function () {
//console.log(event_data);
let num_tickets_to_purchase = genRandom(1, event_data['number_tickets']);

let tickets_to_purchase = [...(await client_contract.get_event_ids(event_data['event_name'])).slice(0, num_tickets_to_purchase)];
let tickets_to_purchase = [...(await client_contract.get_event_ids(event_data['event_name']))[0].slice(0, num_tickets_to_purchase)];

let local_cost = num_tickets_to_purchase * event_data['ticket_cost'];
// update total for total money vendor should have
Expand Down