Skip to content

Commit

Permalink
ADD: tests for aime factory beta
Browse files Browse the repository at this point in the history
  • Loading branch information
cctv2206 committed May 7, 2024
1 parent 9bd8ba5 commit 7e521c1
Show file tree
Hide file tree
Showing 4 changed files with 760 additions and 10 deletions.
283 changes: 283 additions & 0 deletions contracts/aime/AIMeNFTBeta.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Base64.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

contract AIMeNFTBeta is ERC721, Ownable, ERC721Holder {
using Strings for uint256;
IERC20 public aimePower;
uint256 public constant NFT_HOLDING_PERIOD = 30 days;
uint256 public aimePowerReserved;
uint256 public minPrice;
uint256 public nftPriceIncrement;
string public avatar;
address private _factory;
uint256 private _nextTokenId;
mapping(uint256 => AIMeInfo) public tokenContents;

error AIMeNFTUnauthorizedAccount(address account);

event AIMePowerSet(address aimePowerAddress);
event AIMeNFTMinted(
address creator,
address aimeAddress,
uint256 tokenId,
string key,
string infoType,
string data,
uint256 amount
);
event AIMeNFTUpdated(
uint256 tokenId,
string data
);

event TradeNFT(address from, address to, uint256 tokenId, uint256 price);

modifier onlyFactory() {
if (factory() != _msgSender()) {
revert AIMeNFTUnauthorizedAccount(_msgSender());
}
_;
}

struct AIMeInfo {
string key;
string dataType;
string data;
string image;
uint256 amount;
uint256 currentAmount;
uint256 timestamp;
}

constructor(
string memory name_,
string memory symbol_,
string memory avatar_,
string memory bio_,
string memory image_,
uint256 minPrice_,
uint256 nftPriceIncrement_
// address aimePowerAddress
) ERC721(name_, symbol_) {
_factory = _msgSender();
// aimePower = IERC20(aimePowerAddress);
avatar = avatar_;
minPrice = minPrice_;
nftPriceIncrement = nftPriceIncrement_;

// mint initial nft
safeMint(address(this), "basic_prompt", "static", bio_, image_, 0);
}

function factory() public view virtual returns (address) {
return _factory;
}

function setAIMePower(address aimePowerAddress) public onlyFactory {
require(address(aimePower) == address(0), "AIME Power already set");
aimePower = IERC20(aimePowerAddress);
emit AIMePowerSet(aimePowerAddress);
}

function addAIMePowerReserved(uint256 amount) public {
require(address(aimePower) != address(0), "No AIME Power");
require(
aimePower.balanceOf(msg.sender) >= amount,
"balance not enough"
);
require(
aimePower.allowance(msg.sender, address(this)) >= amount,
"allowance not enough"
);
aimePower.transferFrom(msg.sender, address(this), amount);
aimePowerReserved += amount;
}

function safeMint(
address to,
string memory key,
string memory dataType,
string memory data,
string memory image,
uint256 amount
) public onlyFactory returns (uint256) {
uint256 timestamp = 0;
if (to != address(this)) {
require(address(aimePower) != address(0), "No AIME Power");
require(amount >= minPrice, "price too low");
if (
aimePowerReserved >= amount &&
aimePower.balanceOf(address(this)) >= amount
) {
aimePowerReserved -= amount;
timestamp = block.timestamp;
} else {
require(
aimePower.balanceOf(to) >= amount,
"balance not enough"
);
require(
aimePower.allowance(to, address(this)) >= amount,
"allowance not enough"
);
aimePower.transferFrom(to, address(this), amount);
}
}

uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
tokenContents[tokenId] = AIMeInfo(
key,
dataType,
data,
image,
amount,
amount,
timestamp
);

emit AIMeNFTMinted(
tx.origin,
address(this),
tokenId,
key,
dataType,
data,
amount
);

return tokenId;
}

function getSellNFTPrice(uint256 tokenId) public view returns (uint256) {
uint256 duration = block.timestamp - tokenContents[tokenId].timestamp;
if (duration < NFT_HOLDING_PERIOD) {
return tokenContents[tokenId].amount * duration / NFT_HOLDING_PERIOD;
} else {
return tokenContents[tokenId].amount;
}
}

function sellNFT(uint256 tokenId) external {
_safeTransfer(msg.sender, address(this), tokenId, "");
uint256 amount = getSellNFTPrice(tokenId);
aimePower.transfer(msg.sender, amount);
emit TradeNFT(msg.sender, address(this), tokenId, amount);
}

function buyNFT(uint256 tokenId) external {
require(address(aimePower) != address(0), "No AIME Power");
address owner = ownerOf(tokenId);
require(owner != address(0), "Invalid token owner");
uint256 amount;
if (owner == address(this)) {
amount = tokenContents[tokenId].amount;
} else {
amount =
(tokenContents[tokenId].currentAmount * (nftPriceIncrement + 100)) /
100;
tokenContents[tokenId].currentAmount = amount;
}

require(amount > 0, "Not for sale");
require(
aimePower.balanceOf(msg.sender) >= amount,
"balance not enough"
);
require(
aimePower.allowance(msg.sender, address(this)) >= amount,
"allowance not enough"
);
aimePower.transferFrom(msg.sender, owner, amount);
_safeTransfer(owner, msg.sender, tokenId, "");
emit TradeNFT(owner, msg.sender, tokenId, amount);
}

function updateAIMeInfo(
uint256 tokenId,
address owner,
string memory data,
string memory image
) public onlyFactory {
address tokenOwner = ownerOf(tokenId);
require(
tokenOwner == owner && owner != address(0),
"Invalid token owner"
);
tokenContents[tokenId].data = data;
tokenContents[tokenId].image = image;
emit AIMeNFTUpdated(tokenId, data);
}

function tokenURI(
uint256 tokenId
) public view virtual override returns (string memory) {
require(
_exists(tokenId),
"ERC721Metadata: URI query for nonexistent token"
);

string memory tokenName = string(
abi.encodePacked(name(), " #", tokenId.toString())
);
string memory imageUrl = tokenContents[tokenId].image;
string memory tradeUrl = string(
abi.encodePacked(
"https://app-dev.aime.bot/nft/",
Strings.toHexString(uint256(uint160(address(this))), 20),
"/",
tokenId.toString()
)
);

string memory attributes = string(
abi.encodePacked(
"[",
'{"trait_type": "type","value": "',
tokenContents[tokenId].dataType,
'"},',
'{"trait_type": "key","value": "',
tokenContents[tokenId].key,
'"},',
'{"trait_type": "data","value": "',
tokenContents[tokenId].data,
'"}',
"]"
)
);

string memory json = Base64.encode(
bytes(
string(
abi.encodePacked(
'{"name": "',
tokenName,
'", "description": "A block of content of ',
name(),
". Trade at: ",
tradeUrl,
'", "attributes":',
attributes,
', "amount": "',
tokenContents[tokenId].amount.toString(),
'", "image": "',
imageUrl,
'"}'
)
)
)
);
string memory output = string(
abi.encodePacked("data:application/json;base64,", json)
);

return output;
}
}
4 changes: 3 additions & 1 deletion hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@ const config = {
accounts: [
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
"0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d",
"0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"
"0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a",
"0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6",
"0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a"
]
},
hardhat: {},
Expand Down
14 changes: 11 additions & 3 deletions scripts/aime/deploy_aime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,22 @@ async function deployAIMeContractsAll() {
console.log("aimeCreator contract deployed to", aimeCreator.address);
}

async function deployAIMeFactoryLauncher() {
async function deployAIMeFactory() {
const contractFactory = await ethers.getContractFactory("AIMeFactoryV4");
const instance = await contractFactory.deploy();
await instance.deployed();

console.log("AIMeFactory V4 contract deployed to", instance.address);
}

async function deployAIMeFactoryBeta() {
const contractFactory = await ethers.getContractFactory("AIMeFactoryBeta");
const instance = await contractFactory.deploy();
await instance.deployed();

console.log("AIMeFactory [Beta] contract deployed to", instance.address);
}

async function deployAIMePowerLauncher() {
const powerLauncherContractFactory = await ethers.getContractFactory(
"AIMePowerLauncher"
Expand All @@ -53,7 +61,7 @@ async function deployAIMePowerLauncherSepolia() {
console.log("powerLauncher sepolia contract deployed to", powerLauncher.address);
}

async function deployAIMeCreatorLauncher() {
async function deployAIMeCreator() {
const aimeCreatorContractFactory = await ethers.getContractFactory(
"AIMeCreator"
);
Expand All @@ -65,7 +73,7 @@ async function deployAIMeCreatorLauncher() {

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
deployAIMePowerLauncherSepolia().catch((error) => {
deployAIMeCreator().catch((error) => {
console.error(error);
process.exitCode = 1;
});

0 comments on commit 7e521c1

Please sign in to comment.