Skip to content

Commit

Permalink
feat: add PundiAIFX contract
Browse files Browse the repository at this point in the history
  • Loading branch information
nulnut committed Jan 14, 2025
1 parent 93c36e3 commit d8cb88e
Show file tree
Hide file tree
Showing 2 changed files with 219 additions and 0 deletions.
86 changes: 86 additions & 0 deletions solidity/contracts/token/PundiAIFX.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import {ERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";

/* solhint-disable custom-errors */

contract PundiAIFX is
Initializable,
ERC20Upgradeable,
ERC20PermitUpgradeable,
AccessControlUpgradeable,
UUPSUpgradeable
{
bytes32 public constant OWNER_ROLE = keccak256("OWNER_ROLE");
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");

IERC20Upgradeable public fxToken;

/**
* @notice Mints a specified amount of tokens to the recipient's account.
* @dev This function can only be called by an account with the ADMIN_ROLE.
* @param _to The recipient's account.
* @param _amount The amount of tokens to be minted.
*/
function mint(address _to, uint256 _amount) external onlyRole(ADMIN_ROLE) {
_mint(_to, _amount);
}

/**
* @notice Burns a specified amount of tokens from the sender's account.
* @dev This function can only be called by an account with the ADMIN_ROLE.
* @param _to The recipient's account.
* @param _amount The amount of tokens to be burned
*/
function burn(address _to, uint256 _amount) external onlyRole(ADMIN_ROLE) {
_burn(_to, _amount);
}

/**
* @notice Swaps a specified amount of tokens for a smaller amount of new tokens.
* @dev The function calculates the swap amount as 1% of the input amount.
* It then transfers the original tokens from the sender to the contract
* and mints new tokens to the sender.
* @param _amount The amount of tokens to be swapped.
* @return bool Returns true if the swap is successful.
*/
function swap(uint256 _amount) external returns (bool) {
uint256 _swapAmount = _amount / 100;
require(_swapAmount > 0, "swap amount is too small");

require(
fxToken.transferFrom(
_msgSender(),
address(this),
_swapAmount * 100
),
"transferFrom FX failed"
);
_mint(_msgSender(), _swapAmount);
return true;
}

// solhint-disable no-empty-blocks
function _authorizeUpgrade(
address
) internal override onlyRole(OWNER_ROLE) {}
// solhint-disable no-empty-blocks

function initialize(address _fxToken) public virtual initializer {
fxToken = IERC20Upgradeable(_fxToken);

__ERC20_init("Pundi AIFX Token", "PUNDIAI");
__ERC20Permit_init("Pundi AIFX Token");
__AccessControl_init();
__UUPSUpgradeable_init();

_grantRole(DEFAULT_ADMIN_ROLE, _msgSender());
_grantRole(OWNER_ROLE, _msgSender());
}
}
133 changes: 133 additions & 0 deletions solidity/test/pundiaifx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { ethers } from "hardhat";
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
import { expect } from "chai";
import { ERC20TokenTest, PundiAIFX } from "../typechain-types";
import { it } from "mocha";

describe("pundiaifx tests", function () {
let deploy: HardhatEthersSigner;
let user1: HardhatEthersSigner;
let fxToken: ERC20TokenTest;
let pundiAIFX: PundiAIFX;
let totalSupply = "100000000";

beforeEach(async function () {
const signers = await ethers.getSigners();
deploy = signers[0];
user1 = signers[1];

const erc20TokenFactory = await ethers.getContractFactory("ERC20TokenTest");
fxToken = await erc20TokenFactory
.connect(deploy)
.deploy("FX Token", "FX", "18", ethers.parseEther(totalSupply));

const pundiAIFXFactory = await ethers.getContractFactory("PundiAIFX");
const pundiAIFXDeploy = await pundiAIFXFactory.deploy();

const erc1967ProxyFactory = await ethers.getContractFactory("ERC1967Proxy");
const erc1967Proxy = await erc1967ProxyFactory
.connect(deploy)
.deploy(await pundiAIFXDeploy.getAddress(), "0x");

pundiAIFX = await ethers.getContractAt(
"PundiAIFX",
await erc1967Proxy.getAddress()
);
await pundiAIFX.connect(deploy).initialize(await fxToken.getAddress());

await fxToken
.connect(deploy)
.transfer(user1.address, ethers.parseEther(totalSupply));
await fxToken
.connect(user1)
.approve(await pundiAIFX.getAddress(), ethers.parseEther(totalSupply));
});

it("Pundi AIFX", async function () {
expect(await pundiAIFX.name()).to.equal("Pundi AIFX Token");
expect(await pundiAIFX.symbol()).to.equal("PUNDIAI");
expect(await pundiAIFX.decimals()).to.equal(18);
expect(await pundiAIFX.totalSupply()).to.equal(0);
});

it("mint, burn and transfer AIFX", async function () {
expect(await pundiAIFX.balanceOf(user1.address)).to.equal(0);
expect(await pundiAIFX.totalSupply()).to.equal(0);

await pundiAIFX.grantRole(await pundiAIFX.ADMIN_ROLE(), deploy.address);
await pundiAIFX.mint(deploy.address, "1");

await pundiAIFX.approve(user1.address, "1");
await pundiAIFX
.connect(user1)
.transferFrom(deploy.address, user1.address, "1");

expect(await pundiAIFX.balanceOf(user1.address)).to.equal(1);
expect(await pundiAIFX.totalSupply()).to.equal(1);

await pundiAIFX.connect(user1).transfer(deploy.address, "1");

expect(await pundiAIFX.balanceOf(deploy.address)).to.equal(1);

await pundiAIFX.burn(deploy.address, "1");

expect(await pundiAIFX.totalSupply()).to.equal(0);
});

it("check role", async function () {
expect(await pundiAIFX.getRoleAdmin(await pundiAIFX.OWNER_ROLE())).to.equal(
await pundiAIFX.DEFAULT_ADMIN_ROLE()
);
expect(await pundiAIFX.getRoleAdmin(await pundiAIFX.ADMIN_ROLE())).to.equal(
await pundiAIFX.DEFAULT_ADMIN_ROLE()
);

expect(
await pundiAIFX.hasRole(
await pundiAIFX.DEFAULT_ADMIN_ROLE(),
deploy.address
)
).to.equal(true);
expect(
await pundiAIFX.hasRole(await pundiAIFX.OWNER_ROLE(), deploy.address)
).to.equal(true);

expect(
await pundiAIFX.hasRole(await pundiAIFX.ADMIN_ROLE(), user1.address)
).to.equal(false);

await pundiAIFX.grantRole(await pundiAIFX.ADMIN_ROLE(), user1.address);

expect(
await pundiAIFX.hasRole(await pundiAIFX.ADMIN_ROLE(), user1.address)
).to.equal(true);

await pundiAIFX.revokeRole(await pundiAIFX.ADMIN_ROLE(), user1.address);

expect(
await pundiAIFX.hasRole(await pundiAIFX.ADMIN_ROLE(), user1.address)
).to.equal(false);
});

it("swap FX", async function () {
expect(await fxToken.balanceOf(user1.address)).to.equal(
ethers.parseEther(totalSupply)
);
expect(await pundiAIFX.balanceOf(user1.address)).to.equal(0);
expect(await fxToken.balanceOf(await pundiAIFX.getAddress())).to.equal(0);
expect(await pundiAIFX.totalSupply()).to.equal(0);

await pundiAIFX.connect(user1).swap(ethers.parseEther("100"));

expect(await fxToken.balanceOf(user1.address)).to.equal(
ethers.parseEther((Number(totalSupply) - 100).toString())
);
expect(await pundiAIFX.balanceOf(user1.address)).to.equal(
ethers.parseEther("1")
);
expect(await fxToken.balanceOf(await pundiAIFX.getAddress())).to.equal(
ethers.parseEther("100")
);
expect(await pundiAIFX.totalSupply()).to.equal(ethers.parseEther("1"));
});
});

0 comments on commit d8cb88e

Please sign in to comment.