Skip to content
Open
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
7 changes: 7 additions & 0 deletions .solhint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "solhint:recommended",
"rules": {
"compiler-version": ["error", "^0.8.7"],
"func-visibility": ["warn", {"ignoreConstructors": true}]
}
}
2 changes: 1 addition & 1 deletion contracts/Puppeeth.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//SPDX-License-Identifier: MIT
pragma solidity 0.8.9;
pragma solidity ^0.8.9;

// _ _
// _ __ _ _ _ __ _ __ ___ ___| |_| |__
Expand Down
124 changes: 124 additions & 0 deletions contracts/PuppeethAltCoins.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

// _ _
// _ __ _ _ _ __ _ __ ___ ___| |_| |__
// | '_ \| | | | '_ \| '_ \ / _ \/ _ \ __| '_ \
// | |_) | |_| | |_) | |_) | __/ __/ |_| | | |
// | .__/ \__,_| .__/| .__/ \___|\___|\__|_| |_|
// |_| |_| |_|
// _ _ _
// __ _| | |_ ___ ___ (_)_ __ ___
// / _` | | __/ __/ _ \| | '_ \/ __|
// | (_| | | || (_| (_) | | | | \__ \
// \__,_|_|\__\___\___/|_|_| |_|___/
//
// web3 development by Decentralized Software Systems, LLC
// https://puppeeth.art

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Address.sol";

/// @title puppeeth altcoins
/// @author Decentralized Software Systems, LLC
contract PuppeethAltCoins is ERC721Holder, Ownable, ReentrancyGuard {

using Address for address;

// Price.
uint256 constant private PUPPEETH_TOKEN_PRICE = .015 ether;

// Puppeeth contract address.
address private puppeethContract;

// Mapping of alt coins and relative prices.
mapping(address => uint256) private altCoins;

/// @notice Deposit minting funds in ether.
function depositMintingFunds() external payable {
require(msg.value > 0, "Invalid minting funds amount");
}

/// @notice Withdraw minting funds.
function withdrawMintingFunds() external onlyOwner {
uint256 balance = address(this).balance;
payable(msg.sender).transfer(balance);
}

/// @notice Withdraw alt coin.
function withdrawAltCoin(
address coinAddress,
uint256 amt
) external onlyOwner {
require(altCoins[coinAddress] > 0, "Invalid alt coin address");
// Transfer tokens.
coinAddress.functionCall(
abi.encodeWithSignature(
"transfer(address,uint256)",
msg.sender,
amt
),
"Alt coin withdraw failed"
);
}

/// @notice Get puppeeth contract.
function getPuppeethContract() external view returns (address) {
return puppeethContract;
}

/// @notice Set puppeeth contract address.
function setPuppeethContract(address contractAddress) external onlyOwner {
puppeethContract = contractAddress;
}

/// @notice Get alt coin mapping.
function getAltCoin(address coinAddress) external view returns (uint256) {
return altCoins[coinAddress];
}

/// @notice Set alt coin address / price mapping entry.
function setAltCoin(
address coinAddress,
uint256 price
) external onlyOwner {
altCoins[coinAddress] = price;
}

/// @notice Mint by alt coin. Assumes token approval for this contract.
function mintByAltCoin(
address coinAddress,
uint16 tokenId
) external nonReentrant {
// Validate alt coin is registered.
require(altCoins[coinAddress] > 0, "Invalid alt coin address");
// Transfer tokens.
coinAddress.functionCall(
abi.encodeWithSignature(
"transferFrom(address,address,uint256)",
msg.sender,
address(this),
altCoins[coinAddress]
),
"Alt coin payment failed"
);
// Mint.
puppeethContract.functionCallWithValue(
abi.encodeWithSignature("mint(uint16)", tokenId),
PUPPEETH_TOKEN_PRICE,
"NFT mint failed"
);
// Transfer NFT.
puppeethContract.functionCall(
abi.encodeWithSignature(
"transferFrom(address,address,uint256)",
address(this),
msg.sender,
tokenId
),
"NFT transfer failed"
);
}
}
21 changes: 21 additions & 0 deletions contracts/PuppeethCoin.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//SPDX-License-Identifier: NOLICENSE
pragma solidity 0.8.10;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract PuppeethCoin is ERC20 {

// Set constants.
string private constant TOKEN_NAME = "PuppeethCoin";
string private constant TOKEN_SYMBOL = "PUPCOIN";

/**
* @dev Constructor.
*
* Constructor method used in deployment.
*/
constructor(uint256 initialSupply) ERC20(TOKEN_NAME, TOKEN_SYMBOL) {
_mint(msg.sender, initialSupply);
}

}
16 changes: 14 additions & 2 deletions hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,19 @@ const cmcAPIKey = process.env.CMC_API_KEY || "";
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: "0.8.9",
solidity: {
compilers: [
{
version: "0.8.10",
settings: {
optimizer: {
enabled: true,
runs: 800,
},
},
},
],
},
paths: {
artifacts: './app/src/artifacts',
},
Expand All @@ -34,7 +46,7 @@ module.exports = {
},
gasReporter: {
currency: "USD",
enabled: false,
enabled: true,
showTimeSpent: true,
coinmarketcap: cmcAPIKey
},
Expand Down
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@
"ethers": "^5.4.7",
"hardhat": "^2.6.4"
},
"scripts": {
"test": "npx hardhat test",
"chain": "npx hardhat node",
"solhint": "solhint 'contracts/**/*.sol'"
},
"dependencies": {
"@nomiclabs/hardhat-etherscan": "^2.1.7",
"@openzeppelin/contracts": "^4.3.2",
"hardhat-gas-reporter": "^1.0.4"
"hardhat-gas-reporter": "^1.0.4",
"solhint": "^3.3.6"
}
}
17 changes: 17 additions & 0 deletions scripts/deploy-altcoins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const hre = require("hardhat");

async function main() {
const PuppeethAltCoinsContract = await hre.ethers.getContractFactory("PuppeethAltCoins");
const PuppeethAltCoins = await PuppeethAltCoinsContract.deploy();

await PuppeethAltCoins.deployed();

console.log("PuppeethAltCoins deployed to:", PuppeethAltCoins.address);
}

main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
18 changes: 18 additions & 0 deletions scripts/deploy-coin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const hre = require("hardhat");

async function main() {
const totalSupply = ethers.BigNumber.from("1000000000000000000000000000");
const PuppeethCoinContract = await hre.ethers.getContractFactory("PuppeethCoin");
const PuppeethCoin = await PuppeethCoinContract.deploy(totalSupply);

await PuppeethCoin.deployed();

console.log("PuppeethCoin deployed to:", PuppeethCoin.address);
}

main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
153 changes: 153 additions & 0 deletions test/PuppeethAltCoins.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
const { BigNumber } = require("@ethersproject/bignumber");
const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("PuppeethAltCoins", function () {
// Declare common variables
let owner, secondAddress, thirdAddress,
PuppeethAltCoinsContract, PuppeethAltCoins,
PuppeethContract, Puppeeth,
PuppeethCoinContract, PuppeethCoin, totalSupply;


before(async function () {
// Get signers of local chain
[owner, secondAddress, thirdAddress] = await ethers.getSigners();

// Deploy contract instances
PuppeethAltCoinsContract = await ethers.getContractFactory("PuppeethAltCoins");
PuppeethAltCoins = await PuppeethAltCoinsContract.deploy();
await PuppeethAltCoins.deployed();

PuppeethContract = await ethers.getContractFactory("Puppeeth");
Puppeeth = await PuppeethContract.deploy();
await Puppeeth.deployed();

PuppeethCoinContract = await ethers.getContractFactory("PuppeethCoin");
totalSupply = BigNumber.from("1000000000000000000000000000");
PuppeethCoin = await PuppeethCoinContract.deploy(totalSupply);
await PuppeethCoin.deployed();
});

it("should accept a deposit for minting funds", async function () {
const depositAmount = ethers.utils.parseEther(".015")
const overrides = {
value: depositAmount
};
const oldBalance = await owner.getBalance();
await PuppeethAltCoins.depositMintingFunds(overrides);
const newBalance = await owner.getBalance();
expect(oldBalance.sub(newBalance).sub(depositAmount).lt(depositAmount)).to.be.true;
const contractBalance = await ethers.provider.getBalance(PuppeethAltCoins.address);
expect(contractBalance).to.equal(depositAmount);
});

it("should allow only owner to withdraw minting funds", async function () {
await expect(
PuppeethAltCoins.connect(secondAddress).withdrawMintingFunds()
).to.be.revertedWith("caller is not the owner");
});

it("should allow owner to withdraw minting funds", async function () {
const oldBalance = await owner.getBalance();
await PuppeethAltCoins.withdrawMintingFunds();
const newBalance = await owner.getBalance();
expect(newBalance.sub(oldBalance) > 0).to.be.true;
const contractBalance = await ethers.provider.getBalance(PuppeethAltCoins.address);
expect(contractBalance).to.equal(0);
});

it("should allow only owner to set Puppeeth contract address", async function () {
await expect(
PuppeethAltCoins.connect(secondAddress)
.setPuppeethContract(Puppeeth.address)
).to.be.revertedWith("caller is not the owner");
});

it("should allow owner to set Puppeeth contract address", async function () {
expect(
await PuppeethAltCoins.getPuppeethContract()
).to.equal(ethers.constants.AddressZero);
await PuppeethAltCoins.setPuppeethContract(Puppeeth.address);
expect(
await PuppeethAltCoins.getPuppeethContract()
).to.equal(Puppeeth.address);
});

it("should allow only owner to set altcoin address and price", async function () {
let price = BigNumber.from("10000000000000000000000000");
await expect(
PuppeethAltCoins.connect(secondAddress)
.setAltCoin(PuppeethCoin.address, price)
).to.be.revertedWith("caller is not the owner");
});

it("should allow owner to set altcoin address and price", async function () {
let price = BigNumber.from("10000000000000000000000000");
PuppeethAltCoins.setAltCoin(PuppeethCoin.address, price)
expect(
await PuppeethAltCoins.getAltCoin(PuppeethCoin.address)
).to.equal(price);
expect(
await PuppeethAltCoins.getAltCoin(secondAddress.address)
).to.equal(ethers.constants.Zero);
});

it("should mint for the public with minting funds", async function () {
// Add minting funds.
const overrides = {
value: ethers.utils.parseEther("1.5")
};
await PuppeethAltCoins.depositMintingFunds(overrides);

// Confirm alt coin price.
let price = BigNumber.from("10000000000000000000000000");
expect(
await PuppeethAltCoins.getAltCoin(PuppeethCoin.address)
).to.equal(price);

// Send altcoins to second address and confirm balances.
expect(await PuppeethCoin.balanceOf(owner.address)).to.equal(totalSupply);
let amt = BigNumber.from("100000000000000000000000000");
await PuppeethCoin.transfer(secondAddress.address, amt);
expect(await PuppeethCoin.balanceOf(secondAddress.address)).to.equal(amt);

// Approve contract allowances for minting price
// for owner and secondAddress wallets.
await PuppeethCoin.approve(PuppeethAltCoins.address, price);
await PuppeethCoin.connect(secondAddress).approve(PuppeethAltCoins.address, price);

// Mint for owner.
await PuppeethAltCoins.mintByAltCoin(PuppeethCoin.address, 11114);
expect(await Puppeeth.ownerOf(11114)).to.equal(owner.address);
expect(await PuppeethCoin.balanceOf(PuppeethAltCoins.address)).to.equal(price);

// Mint for secondAddress.
await PuppeethAltCoins.connect(secondAddress).mintByAltCoin(PuppeethCoin.address, 11125);
expect(await Puppeeth.ownerOf(11125)).to.equal(secondAddress.address);

// Verify contract ERC20 balance.
expect(await PuppeethCoin.balanceOf(PuppeethAltCoins.address)).to.equal(price.mul(2));
});

it("should allow owner to withdraw alt coin balance", async function () {
let balance = BigNumber.from("10000000000000000000000000");
await expect(
PuppeethAltCoins.connect(secondAddress)
.withdrawAltCoin(PuppeethCoin.address, balance)
).to.be.revertedWith("caller is not the owner");
});

it("should allow owner to withdraw alt coin balance", async function () {
// Verify contract ERC20 balance.
let balance = BigNumber.from("20000000000000000000000000");
expect(await PuppeethCoin.balanceOf(PuppeethAltCoins.address)).to.equal(balance);
let ownerBalance = await PuppeethCoin.balanceOf(owner.address);
// Withdraw balance.
await PuppeethAltCoins.withdrawAltCoin(PuppeethCoin.address, balance);
// Verify balances
expect(await PuppeethCoin.balanceOf(PuppeethAltCoins.address)).to.equal(0);
expect(await PuppeethCoin.balanceOf(owner.address)).to.equal(ownerBalance.add(balance));
});

});
Loading