-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Signed-off-by: Logan Nguyen <[email protected]>
- Loading branch information
1 parent
1b45c1e
commit db6afbb
Showing
2 changed files
with
184 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity ^0.8.20; | ||
|
||
library Balances { | ||
function move(mapping(address => uint256) storage balances, address from, address to, uint amount) internal { | ||
require(balances[from] >= amount); | ||
require(balances[to] + amount >= balances[to]); | ||
balances[from] -= amount; | ||
balances[to] += amount; | ||
} | ||
} | ||
|
||
contract Token { | ||
/// storage states | ||
using Balances for *; | ||
mapping(address => uint256) balances; | ||
mapping(address => mapping(address => uint256)) allowed; | ||
|
||
/// events | ||
event Transfer(address from, address to, uint amount); | ||
event Approval(address owner, address spender, uint amount); | ||
|
||
/// constructor | ||
constructor(uint256 amount) { | ||
balances[msg.sender] = amount; | ||
} | ||
|
||
/// transfer `amount` from `msg.sender` to `to` | ||
function transfer(address to, uint amount) external returns (bool success) { | ||
balances.move(msg.sender, to, amount); | ||
emit Transfer(msg.sender, to, amount); | ||
return true; | ||
} | ||
|
||
/// transfer `amount` from `from` to `to` | ||
function transferFrom(address from, address to, uint amount) external returns (bool success) { | ||
require(allowed[from][msg.sender] >= amount); | ||
allowed[from][msg.sender] -= amount; | ||
balances.move(from, to, amount); | ||
emit Transfer(from, to, amount); | ||
return true; | ||
} | ||
|
||
/// approve an `amount` of allowance for `spender` | ||
function approve(address spender, uint amount) external returns (bool success) { | ||
require(allowed[msg.sender][spender] == 0, ""); | ||
allowed[msg.sender][spender] = amount; | ||
emit Approval(msg.sender, spender, amount); | ||
return true; | ||
} | ||
|
||
/// util method to check balances of addresses | ||
function balanceOf(address tokenOwner) external view returns (uint balance) { | ||
return balances[tokenOwner]; | ||
} | ||
|
||
/// util method to check allowances | ||
function allowance(address owner, address spender) external view returns (uint balance) { | ||
return allowed[owner][spender]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
/*- | ||
* | ||
* Hedera Smart Contracts | ||
* | ||
* Copyright (C) 2023 Hedera Hashgraph, LLC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
*/ | ||
|
||
const { expect } = require('chai') | ||
const { ethers } = require('hardhat') | ||
|
||
describe('@solidityequiv4 Modular Token', () => { | ||
const INITIAL_AMOUNT = 12000 | ||
let modularTokenContract, signers, accountA, accountB | ||
|
||
beforeEach(async () => { | ||
signers = await ethers.getSigners() | ||
accountA = await signers[0].getAddress() | ||
accountB = await signers[1].getAddress() | ||
|
||
const modularTokenContractFactory = await ethers.getContractFactory('Token') | ||
|
||
modularTokenContract = await modularTokenContractFactory.deploy( | ||
INITIAL_AMOUNT | ||
) | ||
}) | ||
|
||
it('Deployment', async () => { | ||
const initialBalance = await modularTokenContract.balanceOf( | ||
accountA // deployer | ||
) | ||
|
||
expect(initialBalance).to.eq(INITIAL_AMOUNT) | ||
expect(ethers.utils.isAddress(modularTokenContract.address)).to.be.true | ||
}) | ||
|
||
it('Should transfer an `amount` of token from `msg.sender` to `to` address', async () => { | ||
const TRANSFER_AMOUNT = 3000 | ||
|
||
// execute transaction | ||
const tx = await modularTokenContract.transfer(accountB, TRANSFER_AMOUNT) | ||
|
||
// retrieve states from event | ||
const receipt = await tx.wait() | ||
const event = receipt.events.map((e) => e.event === 'Transfer' && e)[0] | ||
const [from, to, amount] = event.args | ||
|
||
// retrieve balances after transfer | ||
const accountABalance = await modularTokenContract.balanceOf(accountA) | ||
const accountBBalance = await modularTokenContract.balanceOf(accountB) | ||
|
||
// assertion | ||
expect(from).to.eq(accountA) | ||
expect(to).to.eq(accountB) | ||
expect(amount).to.eq(TRANSFER_AMOUNT) | ||
expect(accountABalance).to.eq(INITIAL_AMOUNT - TRANSFER_AMOUNT) | ||
expect(accountBBalance).to.eq(TRANSFER_AMOUNT) | ||
}) | ||
|
||
it('Should let `msg.sender` approve an `amount` of allowance for `spender`', async () => { | ||
const ALLOWANCE = 3000 | ||
|
||
// execute transaction | ||
const tx = await modularTokenContract.approve(accountB, ALLOWANCE) | ||
|
||
// retrieve states from event | ||
const receipt = await tx.wait() | ||
const event = receipt.events.map((e) => e.event === 'Approval' && e)[0] | ||
const [owner, spender, allowance] = event.args | ||
|
||
// retrieve allowance from contract | ||
const storageAllowance = await modularTokenContract.allowance( | ||
accountA, | ||
accountB | ||
) | ||
|
||
// assertion | ||
expect(owner).to.eq(accountA) | ||
expect(spender).to.eq(accountB) | ||
expect(allowance).to.eq(ALLOWANCE) | ||
expect(storageAllowance).to.eq(ALLOWANCE) | ||
}) | ||
|
||
it('Should let `msg.sender` transfer an `amount` to `to` on behalf of `from`', async () => { | ||
const ALLOWANCE = 3000 | ||
|
||
// accountA first need to approve an allowance for accountB | ||
await modularTokenContract.approve(accountB, ALLOWANCE) | ||
|
||
// execute transferFrom by signer[1] (i.e. accountB) | ||
const tx = await modularTokenContract | ||
.connect(signers[1]) | ||
.transferFrom(accountA, accountB, ALLOWANCE) | ||
|
||
// retrieve states from event | ||
const receipt = await tx.wait() | ||
const event = receipt.events.map((e) => e.event === 'Transfer' && e)[0] | ||
const [from, to, amount] = event.args | ||
|
||
// retrieve balances and allowance from storage | ||
const accountABalance = await modularTokenContract.balanceOf(accountA) | ||
const accountBBalance = await modularTokenContract.balanceOf(accountB) | ||
|
||
// assertion | ||
expect(to).to.eq(accountB) | ||
expect(from).to.eq(accountA) | ||
expect(amount).to.eq(ALLOWANCE) | ||
expect(accountBBalance).to.eq(ALLOWANCE) | ||
expect(accountABalance).to.eq(INITIAL_AMOUNT - ALLOWANCE) | ||
}) | ||
}) |