Commit 153457ae authored by Kegan's avatar Kegan
Browse files

Created SkinZ NFT Contract.

parent 0a0cbdf6
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "hardhat/console.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
/// @title Base64
/// @author Brecht Devos - <[email protected]>
/// @notice Provides a function for encoding some bytes in base64
library Base64 {
string internal constant TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
function encode(bytes memory data) internal pure returns (string memory) {
if (data.length == 0) return '';
// load the table into memory
string memory table = TABLE;
// multiply by 4/3 rounded up
uint256 encodedLen = 4 * ((data.length + 2) / 3);
// add some extra buffer at the end required for the writing
string memory result = new string(encodedLen + 32);
assembly {
// set the actual output length
mstore(result, encodedLen)
// prepare the lookup table
let tablePtr := add(table, 1)
// input ptr
let dataPtr := data
let endPtr := add(dataPtr, mload(data))
// result ptr, jump over length
let resultPtr := add(result, 32)
// run over the input, 3 bytes at a time
for {} lt(dataPtr, endPtr) {}
{
dataPtr := add(dataPtr, 3)
// read 3 bytes
let input := mload(dataPtr)
// write 4 characters
mstore(resultPtr, shl(248, mload(add(tablePtr, and(shr(18, input), 0x3F)))))
resultPtr := add(resultPtr, 1)
mstore(resultPtr, shl(248, mload(add(tablePtr, and(shr(12, input), 0x3F)))))
resultPtr := add(resultPtr, 1)
mstore(resultPtr, shl(248, mload(add(tablePtr, and(shr( 6, input), 0x3F)))))
resultPtr := add(resultPtr, 1)
mstore(resultPtr, shl(248, mload(add(tablePtr, and( input, 0x3F)))))
resultPtr := add(resultPtr, 1)
}
// padding with '='
switch mod(mload(data), 3)
case 1 { mstore(sub(resultPtr, 2), shl(240, 0x3d3d)) }
case 2 { mstore(sub(resultPtr, 1), shl(248, 0x3d)) }
}
return result;
}
}
/**
* @title Precompiled contract that exists in every Arbitrum chain at address(100), 0x0000000000000000000000000000000000000064. Exposes a variety of system-level functionality.
*/
interface ArbSys {
/**
* @notice Get internal version number identifying an ArbOS build
* @return version number as int
*/
function arbOSVersion() external pure returns (uint);
/**
* @notice Get Arbitrum block number (distinct from L1 block number; Arbitrum genesis block has block number 0)
* @return block number as int
*/
function arbBlockNumber() external view returns (uint);
/**
* @notice Send given amount of Eth to dest from sender.
* This is a convenience function, which is equivalent to calling sendTxToL1 with empty calldataForL1.
* @param destination recipient address on L1
* @return unique identifier for this L2-to-L1 transaction.
*/
function withdrawEth(address destination) external payable returns(uint);
/**
* @notice Send a transaction to L1
* @param destination recipient address on L1
* @param calldataForL1 (optional) calldata for L1 contract call
* @return a unique identifier for this L2-to-L1 transaction.
*/
function sendTxToL1(address destination, bytes calldata calldataForL1) external payable returns(uint);
/**
* @notice get the number of transactions issued by the given external account or the account sequence number of the given contract
* @param account target account
* @return the number of transactions issued by the given external account or the account sequence number of the given contract
*/
function getTransactionCount(address account) external view returns(uint256);
/**
* @notice get the value of target L2 storage slot
* This function is only callable from address 0 to prevent contracts from being able to call it
* @param account target account
* @param index target index of storage slot
* @return stotage value for the given account at the given index
*/
function getStorageAt(address account, uint256 index) external view returns (uint256);
/**
* @notice check if current call is coming from l1
* @return true if the caller of this was called directly from L1
*/
function isTopLevelCall() external view returns (bool);
event EthWithdrawal(address indexed destAddr, uint amount);
event L2ToL1Transaction(address caller, address indexed destination, uint indexed uniqueId,
uint indexed batchNumber, uint indexInBatch,
uint arbBlockNum, uint ethBlockNum, uint timestamp,
uint callvalue, bytes data);
}
contract SkinZNFT is ERC721Enumerable, ReentrancyGuard, Ownable {
using Address for address;
struct SkinDefinition {
string image; //ipfs CID for skin artwork
string name;
}
mapping(uint256 => SkinDefinition) internal _nftData; // on NFT claim (mint) we generate random NFT data
struct SkinData {
string image;
string name;
uint256 count;
}
SkinData[] internal _unmintedSkins;
uint256 internal max_token_id;
constructor() ERC721("Skin-Z", "SKZ") Ownable() {
max_token_id = 0;
//NOTE: if we move away from arbitrum, this needs removed!
/*address arbsys_addr = address(0x64);
require(arbsys_addr.isContract(), "could not find arbsys contract at 0x64?! not deployed on arbitrum?");*/
//generate unminted skins
pushSkin(SkinData("QmVWU23fNY5RNp3mpAhGE6ZtZGcs2EnrKkN4Lmf5w96joC","Example NFT", 1)); // push a certain # of these skins to be mintable
pushSkin(SkinData("QmVWU23fNY5RNp3mpAhGE6ZtZGcs2EnrKkN4Lmf5w96joC","Rare NFT", 1)); // push a certain # of these skins to be mintable
pushSkin(SkinData("QmVWU23fNY5RNp3mpAhGE6ZtZGcs2EnrKkN4Lmf5w96joC","Common NFT", 1)); // push a certain # of these skins to be mintable
}
// owner can push new skins for minting
function pushSkin(SkinData memory data) public onlyOwner {
_unmintedSkins.push(data);
max_token_id += data.count;
}
// batch push new mints (for when we deploy a new generation!)
function pushMultiple(SkinData[] memory data) public onlyOwner {
for(uint256 i = 0; i < data.length; i++)
{
_unmintedSkins.push(data[i]);
max_token_id += data[i].count;
}
}
// NOTE: if moving from arbitrum to another blockchain, this source of randomness would need uodated!
// generate a random number (changes between arbitrum and ethereum mainnet!)
function random() internal view returns(uint256)
{
/*ArbSys arbos = ArbSys(address(0x64));
return uint256(keccak256(abi.encodePacked("created by kegan hollern",block.number,arbos.arbBlockNumber(), arbos.arbOSVersion())));*/
return uint256(keccak256(abi.encodePacked("created by kegan hollern",block.number))); //code for non-arbitrum randomization
}
function randomSkin() internal returns (SkinDefinition memory)
{
require(_unmintedSkins.length > 0, "no skins are available to be randomly generated");
uint256 index = 0;
if(_unmintedSkins.length > 1) //if only one skin type remains, no reason to run RNG and waste gas
{
uint256 rng = random();
index = rng % _unmintedSkins.length;
}
string memory skin_cid = _unmintedSkins[index].image;
string memory skin_name = _unmintedSkins[index].name;
if((_unmintedSkins[index].count - 1) == 0) { // no reason to decrement count to 0 (and spend gas) when we just need to remove this item
//remove skin
_unmintedSkins[index] = _unmintedSkins[_unmintedSkins.length-1]; //move last element into the position of our item we want to remove
_unmintedSkins.pop(); //delete last item which is now a duplicate
} else {
_unmintedSkins[index].count--; //more skins of this type remain, just decrement count
}
SkinDefinition memory output = SkinDefinition(skin_cid, skin_name);
return output;
}
function tokenURI(uint256 tokenId) override public view returns (string memory) {
require(_exists(tokenId), "token not claimed");
SkinDefinition memory _data = _nftData[tokenId];
assert(bytes(_data.image).length > 0); //should NEVER happen
//nft metadata is stored on chain instead of on IPFS, this allows each NFT to have a unique name.
string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name":"',_data.name,' #',Strings.toString(tokenId), '", "description": "Skin-Z are NFTs for DayZ Standalone. While not unique, some skins are rarer than others. Owning one allows skins to be applied onto gear in DayZ.", "image": "ipfs://', _data.image, '"}'))));
string memory output = string(abi.encodePacked('data:application/json;base64,', json));
return output;
}
function claim(uint256 tokenId) public nonReentrant {
require(_unmintedSkins.length > 0, "no more skins to claim");
require(tokenId <= max_token_id && tokenId > 0, "Invalid Token ID"); //tokenIds start from 1!
require(!_exists(tokenId), "token already claimed");
_nftData[tokenId] = randomSkin();
assert(bytes(_nftData[tokenId].image).length > 0);
assert(bytes(_nftData[tokenId].name).length > 0);
_safeMint(_msgSender(), tokenId);
}
}
\ No newline at end of file
# Steam<->ETH Linking Contract
Smart contract written in [[Solidity]] for verifying eth wallet ownership for a provided SteamID. Meant for use within applications where SteamID ownership is already validated.
Problem: we need a way to get a player's NFTs from DayZ
......@@ -38,7 +39,21 @@ We need a few key functions
LinkAccount should hash the caller's wallet and map it to the hashed steamid input argument
Verify should extract the mapped wallet steamid link and compare it with the inputs to ensure a match
Verify should extract the mapped wallet steamid link and compare it with the inputs to ensure a match.
## Deploying
This contract is deployed on networks under these addresses
```markdown
| Network | Address |
|:-: |:-: |
| ETH Mainnet | 0x0 |
| Rinkeby Testnet | 0x0 |
| Arbitrum Mainnet | 0x0 |
| Arbitrum Testnet | 0x0 |
```
## Vulnerabilities
......
// We require the Hardhat Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
//
// When running the script with `npx hardhat run <script>` you'll find the Hardhat
// Runtime Environment's members available in the global scope.
const hre = require("hardhat");
async function main() {
// Hardhat always runs the compile task when running scripts with its command
// line interface.
//
// If this script is run directly using `node` you may want to call compile
// manually to make sure everything is compiled
// await hre.run('compile');
// We get the contract to deploy
const skznft = await hre.ethers.getContractFactory("SkinZNFT");
const skz = await skznft.deploy();
await skz.deployed();
console.log("Skin-Z deployed to:", ste.address);
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
// We require the Hardhat Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
//
// When running the script with `npx hardhat run <script>` you'll find the Hardhat
// Runtime Environment's members available in the global scope.
const hre = require("hardhat");
async function main() {
// Hardhat always runs the compile task when running scripts with its command
// line interface.
//
// If this script is run directly using `node` you may want to call compile
// manually to make sure everything is compiled
// await hre.run('compile');
// We get the contract to deploy
const skz = await hre.ethers.getContractAt("SteamToEther",'0xa3146718b0b9F2175e8a7F306b88B0c0DE5ABadE');
console.log(skz);
//TESTS
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
......@@ -2,8 +2,39 @@ const { expect } = require("chai");
const { BigNumber } = require("ethers");
const { ethers } = require("hardhat");
describe("SkinZNFT", function() {
it("can deploy successfully", async function() {
const skznft = await hre.ethers.getContractFactory("SkinZNFT");
const skz = await skznft.deploy();
await skz.deployed();
console.log("Skin-Z deployed to:", skz.address);
});
it("can mint a new nft", async function() {
const skznft = await hre.ethers.getContractFactory("SkinZNFT");
const skz = await skznft.deploy();
await skz.deployed();
console.log("Skin-Z deployed to:", skz.address);
await skz.claim(1);
let uri = await skz.tokenURI(1);
console.log('minted nft #1. Uri: ', uri);
await skz.claim(2);
uri = await skz.tokenURI(2);
console.log('minted nft #2. Uri: ', uri);
await skz.claim(3);
uri = await skz.tokenURI(3);
console.log('minted nft #3. Uri: ', uri);
});
});
/*
describe("SteamToEther", function () {
it("Created valid link. Test link values exist", async function () {
const SteamToEther = await ethers.getContractFactory("SteamToEther");
......@@ -43,4 +74,5 @@ describe("SteamToEther", function() {
});
});
\ No newline at end of file
});
*/
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment