Skip to content

Commit

Permalink
feat: added modular solidity example contract (#494) (#523)
Browse files Browse the repository at this point in the history
Signed-off-by: Logan Nguyen <[email protected]>
  • Loading branch information
quiet-node authored Oct 25, 2023
1 parent 1b45c1e commit db6afbb
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 0 deletions.
61 changes: 61 additions & 0 deletions contracts/solidity/modular/Token.sol
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];
}
}
123 changes: 123 additions & 0 deletions test/solidity/modular/Token.js
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)
})
})

0 comments on commit db6afbb

Please sign in to comment.