Skip to content

Commit

Permalink
Add more transaction tests
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinz917 committed Oct 29, 2021
1 parent 0a01096 commit 93caecd
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 23 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ A thought experiment and prototype: What would an in-game barter system within u

Here I propose ZK Vending Machine, a hypothetical game element that allows users to set up "shop" within a game and trade. New users can contest this shop and overtake the shop as well. Here's how one example could work out:

[[INSERT GRAPHIC HERE]]

## How it works

While venturing through the cyber universe (one like Darkforest), Alice lands on a planet and discovers that on this coordinate, there's a vending machine, and a previous player has set up shop here. The shop is an AMM to trade lets say silver for gold (examples of two in-game currencies). To interact with this vending machine, one know the exact coordinates of the vending machine and be nearby it, so this way only players that have discovered it can do so - this is enforced through a zkSNARK. Alice is happy to find the machine and trades her gold for some silver. Bob, the shop owner, collects the gold passively.
While venturing through the cyberspace (one like Darkforest), Alice lands on a planet and discovers that on this coordinate, there's a vending machine, and a previous player has set up shop here. The shop is an AMM to trade lets say silver for gold (examples of two in-game currencies). To interact with this vending machine, one know the exact coordinates of the vending machine and be nearby it, so this way only players that have discovered it can do so - this is enforced through a zkSNARK. Alice is happy to find the machine and trades her gold for some silver. Bob, the shop owner, collects the gold passively.

A few moments later, fellow space cowboy Charlie also comes across this coordinate and discovers the machine. He doesn't quite like this shop, and thinks he should be setting up shop here instead. So, he attacks the vending machine and replaces it with his own, with potentially a better trading rate. In this case, he "attacks" by staking some in-game tokens and therefore becomes the new owner of this shop. The game designers also give shop owners a boost, effectively subsidizing their activity and incentivizing them to set up shop within the game instead of outside.
Moments later, fellow space cowboy Charlie also comes across this coordinate and discovers this machine. He doesn't quite like this shop, and thinks he should be setting up shop here instead. So, he attacks the vending machine and replaces it with his own (overtake mechanism), with potentially a better trading rate. In this case, he "attacks", in this case by staking some in-game tokens, and therefore becomes the new owner of this shop. The game designers also give shop owners a boost, effectively subsidizing their activity and incentivizing users to set up shop within the game instead of transacting outside.

This is possible because all the vending machines share the same programming interface, effectively allowing users to "install" new shops! Under the hood, it's a proxy contract that changes the destination address which is provided by the new owner.
This is possible because all the vending machines will share the same programming interface, effectively allowing users to "install" new shops! Under the hood, it's points at different contracts and executes the call with some given calldata, governed by the current owner.

Please follow me [here](https://twitter.com/kzdagoof) and reach out [here](https://thekevinz.com/) to jam on ideas!
12 changes: 9 additions & 3 deletions contracts/SampleShop1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@ pragma solidity >=0.6.0 <0.9.0;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./interface/ShopInterface.sol";
import "hardhat/console.sol";

contract SampleShop1 is Shop {
mapping(address => uint256) public reputations;

constructor() {}

event Interaction(uint256 _value, address _sender);
event ExampleInteractionEvent(uint256 _value, address _sender);

function interact() public payable override {
emit Interaction(msg.value, msg.sender);
function interact(address _sender) public payable override {
require(msg.value == 0, "Invalid amount");
reputations[_sender] += 1;
// sample interaction
emit ExampleInteractionEvent(msg.value, msg.sender);
}
}
34 changes: 27 additions & 7 deletions contracts/VendingMachine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ pragma solidity >=0.6.0 <0.9.0;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./RangeVerifier.sol";
import "hardhat/console.sol";

contract VendingMachine is Ownable {
Verifier public rangeVerifier;

mapping(address => uint256) public balances;
mapping(uint256 => address) public destinations;
mapping(uint256 => address) public shopOwners;
mapping(uint256 => uint256) public overtakeFees;
Expand All @@ -22,18 +24,28 @@ contract VendingMachine is Ownable {
}

// Transact
// any user can use this
// TODO: Add mock token. Add ZK proof
function transact(uint256 _locationHash) public payable {
// bytes memory _initializationCalldata = abi.encodeWithSignature("initialize(uint256)", msg.sender, _num);
(bool success, bytes memory data) = destinations[_locationHash].call{ value: msg.value }(abi.encodeWithSignature("interact()"));
// user can transact with shop if within range defined by zkSNARK
// TODO: Add mock token
function transact(
uint256[2] memory _a,
uint256[2][2] memory _b,
uint256[2] memory _c,
uint256[2] memory _input
) public payable onlyInPosition(_a, _b, _c, _input) {
(bool success, bytes memory data) = destinations[_input[0]].call{ value: msg.value }(abi.encodeWithSignature("interact(address)", msg.sender));

emit Transaction(success, msg.sender);
}

// Overtake vending machine
// User becomes shopOwner and is allowed to install new "shops"
function overtake(uint256 _locationHash) public payable {
function overtake(
uint256[2] memory _a,
uint256[2][2] memory _b,
uint256[2] memory _c,
uint256[2] memory _input
) public payable onlyInPosition(_a, _b, _c, _input) {
uint256 _locationHash = _input[0];
require(msg.value > overtakeFees[_locationHash], "Need more stake amount");
overtakeFees[_locationHash] = msg.value;
shopOwners[_locationHash] = msg.sender;
Expand All @@ -51,9 +63,17 @@ contract VendingMachine is Ownable {
destinations[_input[0]] = _destination;
}

// Withdraw all balance for given address
function withdraw() public {
uint256 amount = balances[msg.sender];
require(amount != 0, "Balance is empty");
balances[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}

// Modifiers
modifier onlyShopOwner(uint256 _locationHash) {
require(shopOwners[_locationHash] == msg.sender, "Not owner");
require(shopOwners[_locationHash] == msg.sender, "Not shop owner");
_;
}

Expand Down
2 changes: 1 addition & 1 deletion contracts/interface/ShopInterface.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ pragma solidity >=0.6.0 <0.9.0;

// The universal shop interface that every vending machine owner has to abide by.
interface Shop {
function interact() external payable;
function interact(address _sender) external payable;
}
3 changes: 3 additions & 0 deletions test/helper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
export enum revertMessages {
INVALID_PROOF = "Proof is not valid",
INVALID_OWNER = "Not shop owner",
INVALID_RANGE = "Not in range",
INVALID_STAKE = "Need more stake amount",
}

export const sampleRangeProof = [
Expand Down
26 changes: 17 additions & 9 deletions test/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe("ZK Vending Machine", () => {
});

it("Overtake shop", async () => {
await zkvmContract.overtake(machineLocationHash, { value: 10 });
await zkvmContract.overtake(...sampleRangeProof, { value: 10 });
expect(await zkvmContract.shopOwners(machineLocationHash)).to.be.equal(addr1.address);
expect(await zkvmContract.overtakeFees(machineLocationHash)).to.be.equal(10);
});
Expand All @@ -31,12 +31,20 @@ describe("ZK Vending Machine", () => {
await zkvmContract.install(...sampleRangeProof, sampleShop1Contract.address);
expect(await zkvmContract.destinations(machineLocationHash)).to.be.equal(sampleShop1Contract.address);
});
});

// examples
// await nftContract.mint(...initMintProofsArgs);
// expect(nftContract.mint(...initMintWrongProofArgs)).to.be.revertedWith(revertMessages.INVALID_PROOF);
// expect(await nftContract.balanceOf(addr1.address)).to.be.equal(1); // mint NFT
// const character = await nftContract.characters(0);
// expect(character.cHash).equal(initMintProofsArgs[3][0]);
// expect(character.isRevealed).equal(false);
it("Transact with new shop", async () => {
await zkvmContract.transact(...sampleRangeProof);
expect(await sampleShop1Contract.reputations(addr1.address)).to.be.equal(1);
});

it("Overtake shop", async () => {
await zkvmContract.connect(addr2).overtake(...sampleRangeProof, { value: 20 });
expect(await zkvmContract.shopOwners(machineLocationHash)).to.be.equal(addr2.address);
expect(await zkvmContract.overtakeFees(machineLocationHash)).to.be.equal(20);
});

it("Transact with overtaken shop", async () => {
await zkvmContract.connect(addr2).transact(...sampleRangeProof);
expect(await sampleShop1Contract.reputations(addr2.address)).to.be.equal(1);
});
});

0 comments on commit 93caecd

Please sign in to comment.