diff --git a/packages/contracts/contracts/LYT.sol b/packages/contracts/contracts/LYT.sol index e9eb230..72191d9 100644 --- a/packages/contracts/contracts/LYT.sol +++ b/packages/contracts/contracts/LYT.sol @@ -8,5 +8,9 @@ contract LYT is LoyaltyToken { /* * Public functions */ - constructor(address account_, address feeAccount_) LoyaltyToken("Loyalty Coin", "LYT", account_, feeAccount_) {} + constructor( + address account_, + address feeAccount_, + uint256 maxSupply_ + ) LoyaltyToken("Loyalty Coin", "LYT", account_, feeAccount_, maxSupply_) {} } diff --git a/packages/contracts/contracts/LoyaltyToken.sol b/packages/contracts/contracts/LoyaltyToken.sol index 776f722..b8e4a22 100644 --- a/packages/contracts/contracts/LoyaltyToken.sol +++ b/packages/contracts/contracts/LoyaltyToken.sol @@ -11,6 +11,7 @@ contract LoyaltyToken is BIP20DelegatedTransfer { /* * Storage */ + uint256 public _maxSupply; /* * Public functions @@ -19,15 +20,25 @@ contract LoyaltyToken is BIP20DelegatedTransfer { string memory name_, string memory symbol_, address account_, - address feeAccount_ + address feeAccount_, + uint256 maxSupply_ ) BIP20DelegatedTransfer(name_, symbol_, account_, feeAccount_) { require( IMultiSigWallet(owner).supportsInterface(type(IMultiSigWallet).interfaceId), "LoyaltyToken: Invalid interface ID of multi sig wallet" ); + _maxSupply = maxSupply_; } function mint(uint256 amount) external onlyOwner { + require( + (_maxSupply == 0) || (_maxSupply > 0 && totalSupply() + amount <= _maxSupply), + "LoyaltyToken: The total supply exceeded maximum and rejected" + ); _mint(owner, amount); } + + function maxSupply() public view returns (uint256) { + return _maxSupply; + } } diff --git a/packages/contracts/deploy/main_chain_devnet/deploy.ts b/packages/contracts/deploy/main_chain_devnet/deploy.ts index d2112c5..737fdc7 100644 --- a/packages/contracts/deploy/main_chain_devnet/deploy.ts +++ b/packages/contracts/deploy/main_chain_devnet/deploy.ts @@ -6,7 +6,7 @@ import { BOACoin } from "../../src/utils/Amount"; import { ContractUtils } from "../../src/utils/ContractUtils"; import { LYT, MultiSigWallet, MultiSigWalletFactory } from "../../typechain-types"; -import { BaseContract, Contract, Wallet } from "ethers"; +import { BaseContract, BigNumber, Contract, Wallet } from "ethers"; import fs from "fs"; @@ -198,7 +198,7 @@ async function deployToken(accounts: IAccount, deployment: Deployments) { const factory = await ethers.getContractFactory("LYT"); const contract = (await factory .connect(accounts.deployer) - .deploy(deployment.getContractAddress("MultiSigWallet"), deployment.accounts.feeAccount.address)) as LYT; + .deploy(deployment.getContractAddress("MultiSigWallet"), deployment.accounts.feeAccount.address, BigNumber.from(10).pow(BigNumber.from(28)))) as LYT; await contract.deployed(); await contract.deployTransaction.wait(); diff --git a/packages/contracts/deploy/side_chain_devnet/deploy.ts b/packages/contracts/deploy/side_chain_devnet/deploy.ts index 12fa057..a86fc88 100644 --- a/packages/contracts/deploy/side_chain_devnet/deploy.ts +++ b/packages/contracts/deploy/side_chain_devnet/deploy.ts @@ -6,7 +6,7 @@ import { BOACoin } from "../../src/utils/Amount"; import { ContractUtils } from "../../src/utils/ContractUtils"; import { LYT, MultiSigWallet, MultiSigWalletFactory } from "../../typechain-types"; -import { BaseContract, Contract, Wallet } from "ethers"; +import { BaseContract, BigNumber, Contract, Wallet } from "ethers"; import fs from "fs"; @@ -198,7 +198,7 @@ async function deployToken(accounts: IAccount, deployment: Deployments) { const factory = await ethers.getContractFactory("LYT"); const contract = (await factory .connect(accounts.deployer) - .deploy(deployment.getContractAddress("MultiSigWallet"), deployment.accounts.feeAccount.address)) as LYT; + .deploy(deployment.getContractAddress("MultiSigWallet"), deployment.accounts.feeAccount.address, BigNumber.from(10).pow(BigNumber.from(28)))) as LYT; await contract.deployed(); await contract.deployTransaction.wait(); diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 6095f4e..bb684a0 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -1,6 +1,6 @@ { "name": "loyalty-tokens", - "version": "2.1.1", + "version": "2.3.0", "description": "Smart contracts for the loyalty tokens", "files": [ "**/*.sol" @@ -16,7 +16,8 @@ "formatting:check": "prettier '**/*.{json,sol,ts,js,md}' -c", "formatting:write": "prettier '**/*.{json,sol,ts,js,md}' --write", "test:DelegatedTransfer": "hardhat test test/DelegatedTransfer.test.ts", - "test:MultiSigToken": "hardhat test test/MultiSigToken.test.ts" + "test:MultiSigToken": "hardhat test test/MultiSigToken.test.ts", + "test:MaxSupply": "hardhat test test/MaxSupply.test.ts" }, "repository": { "type": "git", diff --git a/packages/contracts/test/DelegatedTransfer.test.ts b/packages/contracts/test/DelegatedTransfer.test.ts index ff262ad..b4b5b38 100644 --- a/packages/contracts/test/DelegatedTransfer.test.ts +++ b/packages/contracts/test/DelegatedTransfer.test.ts @@ -42,9 +42,9 @@ async function deployMultiSigWallet( : undefined; } -async function deployToken(deployer: Wallet, owner: string, feeAccount: string): Promise { +async function deployToken(deployer: Wallet, owner: string, feeAccount: string, maxSupply: BigNumber): Promise { const factory = await ethers.getContractFactory("LYT"); - const contract = (await factory.connect(deployer).deploy(owner, feeAccount)) as LYT; + const contract = (await factory.connect(deployer).deploy(owner, feeAccount, maxSupply)) as LYT; await contract.deployed(); await contract.deployTransaction.wait(); return contract; @@ -88,7 +88,7 @@ describe("Test for LYT token", () => { it("Create Token, Owner is MultiSigWallet", async () => { assert.ok(multiSigWallet); - token = await deployToken(deployer, multiSigWallet.address, feeAccount.address); + token = await deployToken(deployer, multiSigWallet.address, feeAccount.address, BigNumber.from(0)); assert.deepStrictEqual(await token.getOwner(), multiSigWallet.address); assert.deepStrictEqual(await token.balanceOf(multiSigWallet.address), BigNumber.from(0)); }); diff --git a/packages/contracts/test/MaxSupply.test.ts b/packages/contracts/test/MaxSupply.test.ts new file mode 100644 index 0000000..5d402da --- /dev/null +++ b/packages/contracts/test/MaxSupply.test.ts @@ -0,0 +1,248 @@ +import "@nomiclabs/hardhat-ethers"; +import "@nomiclabs/hardhat-waffle"; +import { ethers } from "hardhat"; + +import { HardhatAccount } from "../src/HardhatAccount"; +import { LYT, MultiSigWallet, MultiSigWalletFactory } from "../typechain-types"; + +import assert from "assert"; +import { BigNumber, Wallet } from "ethers"; +import { ContractUtils } from "../src/utils/ContractUtils"; + +import { expect } from "chai"; + +async function deployMultiSigWalletFactory(deployer: Wallet): Promise { + const factory = await ethers.getContractFactory("MultiSigWalletFactory"); + const contract = (await factory.connect(deployer).deploy()) as MultiSigWalletFactory; + await contract.deployed(); + await contract.deployTransaction.wait(); + return contract; +} + +async function deployMultiSigWallet( + factoryAddress: string, + deployer: Wallet, + owners: string[], + required: number, + seed: BigNumber +): Promise { + const contractFactory = await ethers.getContractFactory("MultiSigWalletFactory"); + const factoryContract = contractFactory.attach(factoryAddress) as MultiSigWalletFactory; + + const address = await ContractUtils.getEventValueString( + await factoryContract.connect(deployer).create("", "", owners, required, seed), + factoryContract.interface, + "ContractInstantiation", + "wallet" + ); + + return address !== undefined + ? ((await ethers.getContractFactory("MultiSigWallet")).attach(address) as MultiSigWallet) + : undefined; +} + +async function deployToken(deployer: Wallet, owner: string, feeAccount: string, maxSupply: BigNumber): Promise { + const factory = await ethers.getContractFactory("LYT"); + const contract = (await factory.connect(deployer).deploy(owner, feeAccount, maxSupply)) as LYT; + await contract.deployed(); + await contract.deployTransaction.wait(); + return contract; +} + +describe("Test for LYT token", () => { + const raws = HardhatAccount.keys.map((m) => new Wallet(m, ethers.provider)); + const [deployer, feeAccount, account0, account1, account2, account3, account4] = raws; + const owners1 = [account0, account1, account2]; + + let multiSigFactory: MultiSigWalletFactory; + let multiSigWallet: MultiSigWallet | undefined; + let token: LYT; + const requiredConfirmations = 2; + let totalSupply = BigNumber.from(0); + + before(async () => { + multiSigFactory = await deployMultiSigWalletFactory(deployer); + assert.ok(multiSigFactory); + }); + + it("Create Wallet by Factory", async () => { + multiSigWallet = await deployMultiSigWallet( + multiSigFactory.address, + deployer, + owners1.map((m) => m.address), + requiredConfirmations, + BigNumber.from(1) + ); + assert.ok(multiSigWallet); + + assert.deepStrictEqual( + await multiSigWallet.getMembers(), + owners1.map((m) => m.address) + ); + + assert.deepStrictEqual(await multiSigFactory.getNumberOfWalletsForMember(account0.address), BigNumber.from(1)); + assert.deepStrictEqual(await multiSigFactory.getNumberOfWalletsForMember(account1.address), BigNumber.from(1)); + assert.deepStrictEqual(await multiSigFactory.getNumberOfWalletsForMember(account2.address), BigNumber.from(1)); + }); + + it("Create Token, Owner is wallet", async () => { + const factory = await ethers.getContractFactory("LYT"); + await expect( + factory + .connect(deployer) + .deploy(account0.address, feeAccount.address, BigNumber.from(10).pow(BigNumber.from(28))) + ).to.be.revertedWith("function call to a non-contract account"); + }); + + it("Create Token, Owner is MultiSigWallet", async () => { + assert.ok(multiSigWallet); + + token = await deployToken( + deployer, + multiSigWallet.address, + feeAccount.address, + BigNumber.from(10).pow(BigNumber.from(28)) + ); + assert.deepStrictEqual(await token.getOwner(), multiSigWallet.address); + assert.deepStrictEqual(await token.balanceOf(multiSigWallet.address), BigNumber.from(0)); + assert.deepStrictEqual(await token.maxSupply(), BigNumber.from(10).pow(BigNumber.from(28))); + assert.deepStrictEqual(await token.totalSupply(), BigNumber.from(0)); + }); + + it("Fail mint initial supply", async () => { + const amount = BigNumber.from(10).pow(BigNumber.from(18)); + await expect(token.connect(account0).mint(amount)).to.be.revertedWith("Only the owner can execute"); + }); + + it("mint 1", async () => { + assert.ok(multiSigWallet); + assert.ok(token); + + const initialSupply = BigNumber.from(10).pow(BigNumber.from(27)).mul(5); + totalSupply = BigNumber.from(initialSupply); + + const mintEncoded = token.interface.encodeFunctionData("mint", [initialSupply]); + + const transactionId = await ContractUtils.getEventValueBigNumber( + await multiSigWallet + .connect(account0) + .submitTransaction("Mint", "Mint 1 token", token.address, 0, mintEncoded), + multiSigWallet.interface, + "Submission", + "transactionId" + ); + assert.ok(transactionId !== undefined); + + const executedTransactionId = await ContractUtils.getEventValueBigNumber( + await multiSigWallet.connect(account1).confirmTransaction(transactionId), + multiSigWallet.interface, + "Execution", + "transactionId" + ); + + // Check that transaction has been executed + assert.deepStrictEqual(transactionId, executedTransactionId); + + // Check balance of target + assert.deepStrictEqual(await token.balanceOf(multiSigWallet.address), totalSupply); + }); + + it("mint 2", async () => { + assert.ok(multiSigWallet); + assert.ok(token); + + const additionalSupply = BigNumber.from(10).pow(BigNumber.from(27)).mul(3); + totalSupply = totalSupply.add(additionalSupply); + + const mintEncoded = token.interface.encodeFunctionData("mint", [additionalSupply]); + + const transactionId = await ContractUtils.getEventValueBigNumber( + await multiSigWallet + .connect(account0) + .submitTransaction("Mint", "Mint 1 token", token.address, 0, mintEncoded), + multiSigWallet.interface, + "Submission", + "transactionId" + ); + assert.ok(transactionId !== undefined); + + const executedTransactionId = await ContractUtils.getEventValueBigNumber( + await multiSigWallet.connect(account1).confirmTransaction(transactionId), + multiSigWallet.interface, + "Execution", + "transactionId" + ); + + // Check that transaction has been executed + assert.deepStrictEqual(transactionId, executedTransactionId); + + // Check balance of target + assert.deepStrictEqual(await token.balanceOf(multiSigWallet.address), totalSupply); + }); + + it("mint 3", async () => { + assert.ok(multiSigWallet); + assert.ok(token); + + const additionalSupply = BigNumber.from(10).pow(BigNumber.from(27)).mul(2); + totalSupply = totalSupply.add(additionalSupply); + + const mintEncoded = token.interface.encodeFunctionData("mint", [additionalSupply]); + + const transactionId = await ContractUtils.getEventValueBigNumber( + await multiSigWallet + .connect(account0) + .submitTransaction("Mint", "Mint 1 token", token.address, 0, mintEncoded), + multiSigWallet.interface, + "Submission", + "transactionId" + ); + assert.ok(transactionId !== undefined); + + const executedTransactionId = await ContractUtils.getEventValueBigNumber( + await multiSigWallet.connect(account1).confirmTransaction(transactionId), + multiSigWallet.interface, + "Execution", + "transactionId" + ); + + // Check that transaction has been executed + assert.deepStrictEqual(transactionId, executedTransactionId); + + // Check balance of target + assert.deepStrictEqual(await token.balanceOf(multiSigWallet.address), totalSupply); + }); + + it("mint 4", async () => { + assert.ok(multiSigWallet); + assert.ok(token); + + const additionalSupply = BigNumber.from(1); + totalSupply = totalSupply.add(additionalSupply); + + const mintEncoded = token.interface.encodeFunctionData("mint", [additionalSupply]); + + const transactionId = await ContractUtils.getEventValueBigNumber( + await multiSigWallet + .connect(account0) + .submitTransaction("Mint", "Mint 1 token", token.address, 0, mintEncoded), + multiSigWallet.interface, + "Submission", + "transactionId" + ); + assert.ok(transactionId !== undefined); + + const executedTransactionId = await ContractUtils.getEventValueBigNumber( + await multiSigWallet.connect(account1).confirmTransaction(transactionId), + multiSigWallet.interface, + "Execution", + "transactionId" + ); + + // Check that transaction has been executed + assert.notDeepStrictEqual(transactionId, executedTransactionId); + + // Check balance of target + assert.deepStrictEqual(await token.balanceOf(multiSigWallet.address), totalSupply.sub(1)); + }); +}); diff --git a/packages/contracts/test/MultiSigToken.test.ts b/packages/contracts/test/MultiSigToken.test.ts index 8cf8dea..ae9337f 100644 --- a/packages/contracts/test/MultiSigToken.test.ts +++ b/packages/contracts/test/MultiSigToken.test.ts @@ -41,9 +41,9 @@ async function deployMultiSigWallet( : undefined; } -async function deployToken(deployer: Wallet, owner: string, feeAccount: string): Promise { +async function deployToken(deployer: Wallet, owner: string, feeAccount: string, maxSupply: BigNumber): Promise { const factory = await ethers.getContractFactory("LYT"); - const contract = (await factory.connect(deployer).deploy(owner, feeAccount)) as LYT; + const contract = (await factory.connect(deployer).deploy(owner, feeAccount, maxSupply)) as LYT; await contract.deployed(); await contract.deployTransaction.wait(); return contract; @@ -86,15 +86,16 @@ describe("Test for LYT token", () => { it("Create Token, Owner is wallet", async () => { const factory = await ethers.getContractFactory("LYT"); - await expect(factory.connect(deployer).deploy(account0.address, feeAccount.address)).to.be.revertedWith( - "function call to a non-contract account" - ); + await expect( + factory.connect(deployer).deploy(account0.address, feeAccount.address, BigNumber.from(0)) + ).to.be.revertedWith("function call to a non-contract account"); }); it("Create Token, Owner is MultiSigWallet", async () => { assert.ok(multiSigWallet); - token = await deployToken(deployer, multiSigWallet.address, feeAccount.address); + token = await deployToken(deployer, multiSigWallet.address, feeAccount.address, BigNumber.from(0)); + assert.deepStrictEqual(await token.getOwner(), multiSigWallet.address); assert.deepStrictEqual(await token.balanceOf(multiSigWallet.address), BigNumber.from(0)); });