├── .gitignore ├── audits └── Certik audit result.pdf ├── README.md ├── contracts ├── libs │ ├── MockBEP20.sol │ ├── BEP20Capped.sol │ ├── IBEP20.sol │ ├── SafeBEP20.sol │ └── BEP20.sol └── NanoByteToken.sol ├── scripts ├── deploy.ts └── deploy-verify.ts ├── hardhat.config.ts ├── package.json └── test ├── mock-bep20.test.ts └── nbt.test.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # hardhat 4 | cache 5 | artifacts 6 | 7 | # secrets 8 | secrets.json 9 | -------------------------------------------------------------------------------- /audits/Certik audit result.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanobytetoken/nbt/HEAD/audits/Certik audit result.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nano Byte Token (NBT) 2 | 3 | This repo contains Solidity smart contract code for Nano Byte Token (NBT), standards-compliant tokens on Binance Smart Chain With Governance . Adhering to standards allows other contract developers to easily incorporate your token into their applications. -------------------------------------------------------------------------------- /contracts/libs/MockBEP20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "./BEP20.sol"; 5 | 6 | contract MockBEP20 is BEP20 { 7 | constructor( 8 | string memory name, 9 | string memory symbol, 10 | uint256 supply 11 | ) BEP20(name, symbol) { 12 | _mint(msg.sender, supply); 13 | 14 | } 15 | } -------------------------------------------------------------------------------- /scripts/deploy.ts: -------------------------------------------------------------------------------- 1 | import hre, {ethers} from 'hardhat'; 2 | 3 | 4 | async function main() { 5 | const factory = await ethers.getContractFactory("NanoByteToken"); 6 | const contract = await factory.deploy(); 7 | 8 | await contract.deployed().then((contract) => { 9 | console.log( 10 | `contract deployed and mined to: ${contract.address}` 11 | ); 12 | }); 13 | 14 | } 15 | 16 | 17 | // We recommend this pattern to be able to use async/await everywhere 18 | // and properly handle errors. 19 | main() 20 | .then(() => process.exit(0)) 21 | .catch((error) => { 22 | console.error(error); 23 | process.exit(1); 24 | }); -------------------------------------------------------------------------------- /scripts/deploy-verify.ts: -------------------------------------------------------------------------------- 1 | import hre, {ethers} from 'hardhat'; 2 | 3 | 4 | async function main() { 5 | const factory = await ethers.getContractFactory("NanoByteToken"); 6 | const contract = await factory.deploy(); 7 | 8 | await contract.deployed().then((contract) => { 9 | console.log( 10 | `contract deployed and mined to: ${contract.address}` 11 | ); 12 | }); 13 | 14 | // verify contract 15 | await hre.run("verify:verify", { 16 | address: contract.address, 17 | constructorArguments: [], 18 | }); 19 | } 20 | 21 | 22 | // We recommend this pattern to be able to use async/await everywhere 23 | // and properly handle errors. 24 | main() 25 | .then(() => process.exit(0)) 26 | .catch((error) => { 27 | console.error(error); 28 | process.exit(1); 29 | }); -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @type import('hardhat/config').HardhatUserConfig 3 | */ 4 | import { task } from "hardhat/config"; 5 | import "@nomiclabs/hardhat-waffle" 6 | import "@nomiclabs/hardhat-etherscan"; 7 | let { 8 | mnemonic, 9 | bscscanApiKey, 10 | privateKey 11 | } = require("./secrets.json"); 12 | 13 | 14 | task("accounts", "Prints the list of accounts", async (taskArgs, hre) => { 15 | const accounts = await hre.ethers.getSigners(); 16 | 17 | for (const account of accounts) { 18 | console.log(await account.address); 19 | } 20 | }); 21 | 22 | export default { 23 | networks: { 24 | testnet: { 25 | url: `https://data-seed-prebsc-1-s1.binance.org:8545`, 26 | accounts: [privateKey] 27 | }, 28 | mainnet: { 29 | url: `https://bsc-dataseed.binance.org/`, 30 | accounts: {mnemonic: mnemonic} 31 | } 32 | }, 33 | 34 | etherscan: { 35 | // Your API key for Etherscan 36 | // Obtain one at https://bscscan.com/ 37 | apiKey: bscscanApiKey 38 | }, 39 | solidity: "0.8.4", 40 | }; -------------------------------------------------------------------------------- /contracts/libs/BEP20Capped.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts v4.3.2 (token/ERC20/extensions/ERC20Capped.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import "./BEP20.sol"; 7 | 8 | /** 9 | * @dev Extension of {ERC20} that adds a cap to the supply of tokens. 10 | */ 11 | abstract contract BEP20Capped is BEP20 { 12 | uint256 private immutable _cap; 13 | 14 | /** 15 | * @dev Sets the value of the `cap`. This value is immutable, it can only be 16 | * set once during construction. 17 | */ 18 | constructor(uint256 cap_) { 19 | require(cap_ > 0, "BEP20Capped: cap is 0"); 20 | _cap = cap_; 21 | } 22 | 23 | /** 24 | * @dev Returns the cap on the token's total supply. 25 | */ 26 | function cap() public view virtual returns (uint256) { 27 | return _cap; 28 | } 29 | 30 | /** 31 | * @dev See {ERC20-_mint}. 32 | */ 33 | function _mint(address account, uint256 amount) internal virtual override { 34 | require(BEP20.totalSupply() + amount <= cap(), "BEP20Capped: cap exceeded"); 35 | super._mint(account, amount); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nbt", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "compile": "npx hardhat compile", 8 | "test": "npx hardhat test", 9 | "deploy:local": "npx hardhat run scripts/deploy.ts --network localhost", 10 | "deploy:testnet": "npx hardhat run scripts/deploy.ts --network testnet", 11 | "deploy-verify:testnet": "npx hardhat run scripts/deploy-verify.ts --network testnet", 12 | "deploy:mainnet": "npx hardhat run scripts/deploy.ts --network mainnet", 13 | "deploy-verify:mainnet": "npx hardhat run scripts/deploy-verify.ts --network mainnet", 14 | "sol-merger":"npx sol-merger contracts/NanoByteToken.sol ./build" 15 | }, 16 | "keywords": [], 17 | "author": "NanoByte", 18 | "homepage": "https://www.nanobytetoken.io", 19 | "devDependencies": { 20 | "@nomiclabs/hardhat-ethers": "^2.0.2", 21 | "@nomiclabs/hardhat-etherscan": "^2.1.7", 22 | "@nomiclabs/hardhat-waffle": "^2.0.1", 23 | "@openzeppelin/contracts": "^4.3.2", 24 | "@types/chai": "^4.2.22", 25 | "@types/mocha": "^9.0.0", 26 | "@types/node": "^16.11.1", 27 | "chai": "^4.3.4", 28 | "ethereum-waffle": "^3.4.0", 29 | "ethers": "^5.5.1", 30 | "hardhat": "^2.6.6", 31 | "sol-merger": "^3.1.0", 32 | "ts-node": "^10.3.0", 33 | "typescript": "^4.4.4" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/mock-bep20.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { ethers } from "hardhat"; 3 | import chai from "chai"; 4 | import { solidity } from "ethereum-waffle"; 5 | import { beforeEach } from "mocha"; 6 | import { Contract } from "ethers"; 7 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 8 | 9 | chai.use(solidity); 10 | 11 | describe("BEP 20 MOCK", () => { 12 | let contract: Contract; 13 | let owner: SignerWithAddress; 14 | let address1: SignerWithAddress; 15 | let supply=ethers.utils.parseEther('1000'); 16 | 17 | 18 | beforeEach(async () => { 19 | const Bep20Factory = await ethers.getContractFactory( 20 | "MockBEP20" 21 | ); 22 | [owner, address1] = await ethers.getSigners(); 23 | contract = await Bep20Factory.deploy( 24 | "Mock Bep 20", 25 | "MBT", 26 | supply 27 | ); 28 | }); 29 | 30 | it("Should set the right Total Supply", async () => { 31 | expect(await contract.totalSupply()).to.equal(supply); 32 | }); 33 | 34 | it("Should set the right Name", async () => { 35 | expect(await contract.name()).to.equal("Mock Bep 20"); 36 | }); 37 | 38 | it("Should set the right Symbol", async () => { 39 | expect(await contract.symbol()).to.equal("MBT"); 40 | }); 41 | 42 | it("Should set the right Owner", async () => { 43 | expect(await contract.getOwner()).to.equal(owner.address); 44 | }); 45 | 46 | }); 47 | -------------------------------------------------------------------------------- /contracts/libs/IBEP20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.4; 3 | 4 | interface IBEP20 { 5 | /** 6 | * @dev Returns the amount of tokens in existence. 7 | */ 8 | function totalSupply() external view returns (uint256); 9 | 10 | /** 11 | * @dev Returns the token decimals. 12 | */ 13 | function decimals() external view returns (uint8); 14 | 15 | /** 16 | * @dev Returns the token symbol. 17 | */ 18 | function symbol() external view returns (string memory); 19 | 20 | /** 21 | * @dev Returns the token name. 22 | */ 23 | function name() external view returns (string memory); 24 | 25 | /** 26 | * @dev Returns the bep token owner. 27 | */ 28 | function getOwner() external view returns (address); 29 | 30 | /** 31 | * @dev Returns the amount of tokens owned by `account`. 32 | */ 33 | function balanceOf(address account) external view returns (uint256); 34 | 35 | /** 36 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 37 | * 38 | * Returns a boolean value indicating whether the operation succeeded. 39 | * 40 | * Emits a {Transfer} event. 41 | */ 42 | function transfer(address recipient, uint256 amount) external returns (bool); 43 | 44 | /** 45 | * @dev Returns the remaining number of tokens that `spender` will be 46 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 47 | * zero by default. 48 | * 49 | * This value changes when {approve} or {transferFrom} are called. 50 | */ 51 | function allowance(address _owner, address spender) external view returns (uint256); 52 | 53 | /** 54 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 55 | * 56 | * Returns a boolean value indicating whether the operation succeeded. 57 | * 58 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 59 | * that someone may use both the old and the new allowance by unfortunate 60 | * transaction ordering. One possible solution to mitigate this race 61 | * condition is to first reduce the spender's allowance to 0 and set the 62 | * desired value afterwards: 63 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 64 | * 65 | * Emits an {Approval} event. 66 | */ 67 | function approve(address spender, uint256 amount) external returns (bool); 68 | 69 | /** 70 | * @dev Moves `amount` tokens from `sender` to `recipient` using the 71 | * allowance mechanism. `amount` is then deducted from the caller's 72 | * allowance. 73 | * 74 | * Returns a boolean value indicating whether the operation succeeded. 75 | * 76 | * Emits a {Transfer} event. 77 | */ 78 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 79 | 80 | /** 81 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 82 | * another (`to`). 83 | * 84 | * Note that `value` may be zero. 85 | */ 86 | event Transfer(address indexed from, address indexed to, uint256 value); 87 | 88 | /** 89 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 90 | * a call to {approve}. `value` is the new allowance. 91 | */ 92 | event Approval(address indexed owner, address indexed spender, uint256 value); 93 | } -------------------------------------------------------------------------------- /contracts/libs/SafeBEP20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./IBEP20.sol"; 6 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 7 | import "@openzeppelin/contracts/utils/Address.sol"; 8 | 9 | /** 10 | * @title SafeBEP20 11 | * @dev Wrappers around BEP20 operations that throw on failure (when the token 12 | * contract returns false). Tokens that return no value (and instead revert or 13 | * throw on failure) are also supported, non-reverting calls are assumed to be 14 | * successful. 15 | * To use this library you can add a `using SafeBEP20 for IBEP20;` statement to your contract, 16 | * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. 17 | */ 18 | library SafeBEP20 { 19 | using SafeMath for uint256; 20 | using Address for address; 21 | 22 | function safeTransfer(IBEP20 token, address to, uint256 value) internal { 23 | _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); 24 | } 25 | 26 | function safeTransferFrom(IBEP20 token, address from, address to, uint256 value) internal { 27 | _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); 28 | } 29 | 30 | /** 31 | * @dev Deprecated. This function has issues similar to the ones found in 32 | * {IBEP20-approve}, and its usage is discouraged. 33 | * 34 | * Whenever possible, use {safeIncreaseAllowance} and 35 | * {safeDecreaseAllowance} instead. 36 | */ 37 | function safeApprove(IBEP20 token, address spender, uint256 value) internal { 38 | // safeApprove should only be called when setting an initial allowance, 39 | // or when resetting it to zero. To increase and decrease it, use 40 | // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' 41 | // solhint-disable-next-line max-line-length 42 | require((value == 0) || (token.allowance(address(this), spender) == 0), 43 | "SafeBEP20: approve from non-zero to non-zero allowance" 44 | ); 45 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); 46 | } 47 | 48 | function safeIncreaseAllowance(IBEP20 token, address spender, uint256 value) internal { 49 | uint256 newAllowance = token.allowance(address(this), spender).add(value); 50 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 51 | } 52 | 53 | function safeDecreaseAllowance(IBEP20 token, address spender, uint256 value) internal { 54 | uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeBEP20: decreased allowance below zero"); 55 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 56 | } 57 | 58 | /** 59 | * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement 60 | * on the return value: the return value is optional (but if data is returned, it must not be false). 61 | * @param token The token targeted by the call. 62 | * @param data The call data (encoded using abi.encode or one of its variants). 63 | */ 64 | function _callOptionalReturn(IBEP20 token, bytes memory data) private { 65 | // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since 66 | // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that 67 | // the target address contains contract code and also asserts for success in the low-level call. 68 | 69 | bytes memory returndata = address(token).functionCall(data, "SafeBEP20: low-level call failed"); 70 | if (returndata.length > 0) { // Return data is optional 71 | // solhint-disable-next-line max-line-length 72 | require(abi.decode(returndata, (bool)), "SafeBEP20: BEP20 operation did not succeed"); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /test/nbt.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { ethers, network } from "hardhat"; 3 | import chai from "chai"; 4 | import { solidity } from "ethereum-waffle"; 5 | import { BigNumber, Contract, Signature,utils } from "ethers"; 6 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 7 | 8 | chai.use(solidity); 9 | 10 | describe("NBT", () => { 11 | let contract: Contract; 12 | let owner: SignerWithAddress; 13 | let alice: SignerWithAddress; 14 | let bob: SignerWithAddress; 15 | let tom: SignerWithAddress; 16 | let delegator: SignerWithAddress; 17 | let delegatee: SignerWithAddress; 18 | let capped:BigNumber =ethers.utils.parseEther('10000000000'); 19 | let firstMint:BigNumber =ethers.utils.parseEther('5500000000'); 20 | let secondMint:BigNumber =capped.sub(firstMint); 21 | 22 | before(async () => { 23 | const NBTFactory = await ethers.getContractFactory( 24 | "NanoByteToken" 25 | ); 26 | [owner, alice, bob, tom, delegator, delegatee ] = await ethers.getSigners(); 27 | contract = await NBTFactory.deploy(); 28 | }); 29 | 30 | it("Should the right cap", async () => { 31 | expect(await contract.cap()).to.equal(capped); 32 | }); 33 | 34 | it("Should the right Name", async () => { 35 | expect(await contract.name()).to.equal("Nano Byte Token"); 36 | }); 37 | 38 | it("Should the right Symbol", async () => { 39 | expect(await contract.symbol()).to.equal("NBT"); 40 | }); 41 | 42 | it("Should the right Owner", async () => { 43 | expect(await contract.getOwner()).to.equal(owner.address); 44 | }); 45 | 46 | it('Should be 18 decimals', async () => { 47 | expect(await contract.decimals()).to.equal(18); 48 | }) 49 | 50 | it("Should the right total supply after min", async () => { 51 | // https://ethereum.stackexchange.com/a/100009 52 | await contract['mint(address,uint256)'](owner.address,firstMint); 53 | expect(await contract.totalSupply()).to.equal(firstMint); 54 | }); 55 | 56 | it("Should failed : Mint over Max cap", async function(){ 57 | await expect(contract['mint(address,uint256)'](owner.address,firstMint)) 58 | .to.be.revertedWith('BEP20Capped: cap exceeded'); 59 | }); 60 | 61 | it("Should the right total supply after minting again and same with max cap", async () => { 62 | // https://ethereum.stackexchange.com/a/100009 63 | await contract['mint(address,uint256)'](owner.address,secondMint); 64 | expect(await contract.totalSupply()).to.equal(capped); 65 | }); 66 | 67 | 68 | it("Mint over Max cap", async function(){ 69 | await expect(contract['mint(address,uint256)'](owner.address,firstMint)) 70 | .to.be.revertedWith('BEP20Capped: cap exceeded'); 71 | 72 | await expect(contract['mint(address,uint256)'](owner.address,1)) 73 | .to.be.revertedWith('BEP20Capped: cap exceeded'); 74 | }); 75 | 76 | 77 | it('Should be success transfer', async () => { 78 | await contract.transfer(alice.address, 1000); 79 | expect(await contract.balanceOf(alice.address)).to.equal(1000); 80 | }) 81 | 82 | it("Should failed transfer more than amount", async function(){ 83 | await expect(contract.transfer(alice.address, capped)) 84 | .to.be.revertedWith('BEP20: transfer amount exceeds balance'); 85 | 86 | await expect(contract.connect(alice).transfer(bob.address, 1001)) 87 | .to.be.revertedWith('BEP20: transfer amount exceeds balance'); 88 | }); 89 | 90 | it('burn should be success', async () => { 91 | await contract.connect(alice).burn(100); 92 | expect(await contract.balanceOf(alice.address)).to.equal(900); 93 | expect(await contract.totalSupply()).to.equal(capped.sub(100)); 94 | }) 95 | 96 | it('burn should be failed execed balance', async () => { 97 | await expect(contract.connect(alice).burn(10000)) 98 | .to.be.revertedWith('BEP20: burn amount exceeds balance'); 99 | 100 | await expect(contract.connect(bob).burn(100)) 101 | .to.be.revertedWith('BEP20: burn amount exceeds balance'); 102 | }) 103 | 104 | it('approve', async () => { 105 | await contract.connect(alice).approve(owner.address, 100); 106 | let allowance = await contract.allowance(alice.address,owner.address); 107 | expect(allowance).to.equal(100); 108 | }) 109 | 110 | it('increase allowance', async () => { 111 | await contract.connect(alice).increaseAllowance(owner.address, 400); 112 | let allowance = await contract.allowance(alice.address,owner.address); 113 | expect(allowance).to.equal(500); 114 | }) 115 | 116 | it('decrease allowance', async () => { 117 | await contract.connect(alice).decreaseAllowance(owner.address, 300); 118 | let allowance = await contract.allowance(alice.address,owner.address); 119 | expect(allowance).to.equal(200); 120 | }) 121 | 122 | it('burn form allowance failed amount exceeds allowance', async () => { 123 | let allowance = await contract.allowance(alice.address,owner.address); 124 | expect(allowance).to.equal(200); 125 | 126 | await expect(contract.burnFrom(alice.address,250)) 127 | .to.be.revertedWith('BEP20: burn amount exceeds allowance'); 128 | }) 129 | 130 | it('burn form allowance failed not have allowance', async () => { 131 | let allowance = await contract.allowance(alice.address,bob.address); 132 | expect(allowance).to.equal(0); 133 | 134 | await expect(contract.connect(bob).burnFrom(alice.address,150)) 135 | .to.be.revertedWith('BEP20: burn amount exceeds allowance'); 136 | }) 137 | 138 | it('burn form allowance', async () => { 139 | await contract.burnFrom(alice.address,150); 140 | expect(await contract.balanceOf(alice.address)).to.equal(750); 141 | expect(await contract.totalSupply()).to.equal(capped.sub(100+150)); 142 | }) 143 | 144 | it('allowance should be deduct after burn form', async () => { 145 | let allowance = await contract.allowance(alice.address,owner.address); 146 | expect(allowance).to.equal(50); 147 | }) 148 | 149 | it('transfer form failed amount exceeds allowance', async () => { 150 | await expect(contract.transferFrom(alice.address,bob.address,100)) 151 | .to.be.revertedWith('BEP20: transfer amount exceeds allowance'); 152 | }) 153 | 154 | 155 | it('should be success transfer form', async () => { 156 | await contract.transferFrom(alice.address,bob.address,50); 157 | expect(await contract.balanceOf(alice.address)).to.equal(700); 158 | expect(await contract.balanceOf(bob.address)).to.equal(50); 159 | }) 160 | 161 | it('tom current votes shoud be same with alice balance', async () => { 162 | await contract.connect(alice).delegate(tom.address); 163 | expect(await contract.getCurrentVotes(tom.address)).to.equal(700); 164 | }) 165 | 166 | it('tom current votes shoud be deduct because alice transfer', async () => { 167 | await contract.connect(alice).transfer(bob.address, 200); 168 | expect(await contract.getCurrentVotes(tom.address)).to.equal(500); 169 | }) 170 | 171 | it('bob current votes shoud be same with the balance becuase he delegate to selft address', async () => { 172 | await contract.connect(bob).delegate(bob.address); 173 | expect(await contract.getCurrentVotes(bob.address)).to.equal(250); 174 | 175 | await contract.transfer(bob.address, 100); 176 | expect(await contract.getCurrentVotes(bob.address)).to.equal(350); 177 | }) 178 | 179 | it('tom current votes not incrase after owner transfer, because tom not doing self delegate', async () => { 180 | await contract.transfer(tom.address, 100); 181 | expect(await contract.getCurrentVotes(tom.address)).to.equal(500); 182 | }) 183 | 184 | 185 | it("delegates on behalf of the signature", async () => { 186 | const nonce = (await contract.nonces(delegator.address)).toString(); 187 | const expiry = 10e9; 188 | const { v, r, s } = await signDelegation(contract.address, network.config.chainId!, delegator.address, delegatee.address, nonce, expiry); 189 | const actualDelegationBefore = await contract.delegates(delegator.address); 190 | expect(actualDelegationBefore).to.equal('0x0000000000000000000000000000000000000000'); 191 | const tx = await contract.delegateBySig(delegatee.address, nonce, expiry, v, r, s); 192 | const actualDelegationAfter = await contract.delegates(delegator.address); 193 | expect(actualDelegationAfter).to.equal(delegatee.address); 194 | 195 | await expect(Promise.resolve(tx)) 196 | .to.emit(contract, 'DelegateChanged') 197 | .withArgs(delegator.address, '0x0000000000000000000000000000000000000000', delegatee.address); 198 | 199 | }); 200 | 201 | it("reverts delegate signature the nonce is bad ", async () => { 202 | const nonce = 9; 203 | const expiry = 10e9; 204 | const { v, r, s } = await signDelegation(contract.address, network.config.chainId!, delegator.address, delegatee.address, nonce, expiry); 205 | await expect(contract.delegateBySig(delegatee.address, nonce, expiry, v, r, s)).to.be.revertedWith('invalid nonce'); 206 | }); 207 | 208 | it("transfer ownership to delegator", async () => { 209 | const ownerAddressBefore = await contract.owner(); 210 | expect(ownerAddressBefore).to.equal(owner.address); 211 | 212 | await contract.transferOwnership(delegator.address); 213 | 214 | const ownerAddressAfter = await contract.owner(); 215 | expect(ownerAddressAfter).to.equal(delegator.address); 216 | 217 | }); 218 | }); 219 | 220 | async function signDelegation( 221 | contractAddress:string, 222 | chainId: number, 223 | delegatorAddr: string, 224 | delegateeAddr: string, 225 | nonce: number, 226 | expiry: number 227 | ): Promise { 228 | const EIP712Domain = { 229 | name: 'Nano Byte Token', 230 | chainId: chainId, 231 | verifyingContract: contractAddress, 232 | }; 233 | const types = { 234 | Delegation: [ 235 | { name: "delegatee", type: "address" }, 236 | { name: "nonce", type: "uint256" }, 237 | { name: "expiry", type: "uint256" }, 238 | ], 239 | }; 240 | const value = { delegatee: delegateeAddr, nonce: nonce, expiry: expiry }; 241 | const digest = utils._TypedDataEncoder.hash(EIP712Domain, types, value); 242 | return getSigningKey(delegatorAddr).signDigest(digest); 243 | } 244 | 245 | function getSigningKey(address: string): utils.SigningKey { 246 | const { initialIndex, count, path, mnemonic } = network.config.accounts as { 247 | initialIndex: number; 248 | count: number; 249 | path: string; 250 | mnemonic: string; 251 | }; 252 | const parentNode = utils.HDNode.fromMnemonic(mnemonic).derivePath(path); 253 | for (let index = initialIndex; index < initialIndex + count; index++) { 254 | const node = parentNode.derivePath(index.toString()); 255 | if (node.address == address) { 256 | return new utils.SigningKey(node.privateKey); 257 | } 258 | } 259 | throw `No private key found for address ${address}`; 260 | } 261 | -------------------------------------------------------------------------------- /contracts/NanoByteToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./libs/BEP20Capped.sol"; 6 | // NBT with Governance. 7 | contract NanoByteToken is BEP20Capped { 8 | using SafeMath for uint256; 9 | 10 | constructor() BEP20('NanoByte Token','NBT') BEP20Capped(10000000000000000000000000000) {} 11 | 12 | function mint(uint256 _amount) external onlyOwner override returns (bool) { 13 | _mint(_msgSender(), _amount); 14 | _moveDelegates(address(0), _delegates[_msgSender()], _amount); 15 | return true; 16 | } 17 | 18 | function mint(address _to, uint256 _amount) external onlyOwner { 19 | _mint(_to, _amount); 20 | _moveDelegates(address(0), _delegates[_to], _amount); 21 | } 22 | 23 | function burn(uint256 _amount) external { 24 | _burn(_msgSender(), _amount); 25 | _moveDelegates(_delegates[_msgSender()], address(0), _amount); 26 | } 27 | 28 | function burnFrom(address _from, uint256 _amount) external{ 29 | _burnFrom(_from, _amount); 30 | _moveDelegates(_delegates[_from], address(0), _amount); 31 | } 32 | 33 | 34 | // transfers delegate authority when sending a token. 35 | // https://medium.com/bulldax-finance/sushiswap-delegation-double-spending-bug-5adcc7b3830f 36 | function _transfer(address sender, address recipient, uint256 amount) internal override virtual { 37 | super._transfer(sender, recipient, amount); 38 | _moveDelegates(_delegates[sender], _delegates[recipient], amount); 39 | } 40 | 41 | 42 | // Copied and modified from YAM code: 43 | // https://github.com/yam-finance/yam-protocol/blob/master/contracts/token/YAMGovernanceStorage.sol 44 | // https://github.com/yam-finance/yam-protocol/blob/master/contracts/token/YAMGovernance.sol 45 | // Which is copied and modified from COMPOUND: 46 | // https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/Comp.sol 47 | 48 | /// @notice A record of each accounts delegate 49 | mapping (address => address) internal _delegates; 50 | 51 | /// @notice A checkpoint for marking number of votes from a given block 52 | struct Checkpoint { 53 | uint32 fromBlock; 54 | uint256 votes; 55 | } 56 | 57 | /// @notice A record of votes checkpoints for each account, by index 58 | mapping (address => mapping (uint32 => Checkpoint)) public checkpoints; 59 | 60 | /// @notice The number of checkpoints for each account 61 | mapping (address => uint32) public numCheckpoints; 62 | 63 | /// @notice The EIP-712 typehash for the contract's domain 64 | bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); 65 | 66 | /// @notice The EIP-712 typehash for the delegation struct used by the contract 67 | bytes32 public constant DELEGATION_TYPEHASH = keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); 68 | 69 | /// @notice A record of states for signing / validating signatures 70 | mapping (address => uint) public nonces; 71 | 72 | /// @notice An event thats emitted when an account changes its delegate 73 | event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate); 74 | 75 | /// @notice An event thats emitted when a delegate account's vote balance changes 76 | event DelegateVotesChanged(address indexed delegate, uint previousBalance, uint newBalance); 77 | 78 | /** 79 | * @notice Gets associated delegatee given the delegator` 80 | * @param delegator The address to get delegatee for 81 | * @return The delegator of the delegate 82 | */ 83 | function delegates(address delegator) 84 | external 85 | view 86 | returns (address) 87 | { 88 | return _delegates[delegator]; 89 | } 90 | 91 | /** 92 | * @notice Delegate votes from `msg.sender` to `delegatee` 93 | * @param delegatee The address to delegate votes to 94 | */ 95 | function delegate(address delegatee) external { 96 | _delegate(msg.sender, delegatee); 97 | } 98 | 99 | /** 100 | * @notice Delegates votes from signatory to `delegatee` 101 | * @param delegatee The address to delegate votes to 102 | * @param nonce The contract state required to match the signature 103 | * @param expiry The time at which to expire the signature 104 | * @param v The recovery byte of the signature 105 | * @param r Half of the ECDSA signature pair 106 | * @param s Half of the ECDSA signature pair 107 | */ 108 | function delegateBySig( 109 | address delegatee, 110 | uint nonce, 111 | uint expiry, 112 | uint8 v, 113 | bytes32 r, 114 | bytes32 s 115 | ) 116 | external 117 | { 118 | bytes32 domainSeparator = keccak256( 119 | abi.encode( 120 | DOMAIN_TYPEHASH, 121 | keccak256(bytes(name())), 122 | getChainId(), 123 | address(this) 124 | ) 125 | ); 126 | 127 | bytes32 structHash = keccak256( 128 | abi.encode( 129 | DELEGATION_TYPEHASH, 130 | delegatee, 131 | nonce, 132 | expiry 133 | ) 134 | ); 135 | 136 | bytes32 digest = keccak256( 137 | abi.encodePacked( 138 | "\x19\x01", 139 | domainSeparator, 140 | structHash 141 | ) 142 | ); 143 | 144 | address signatory = ecrecover(digest, v, r, s); 145 | require(signatory != address(0), "NBT::delegateBySig: invalid signature"); 146 | require(nonce == nonces[signatory], "NBT::delegateBySig: invalid nonce"); 147 | 148 | nonces[signatory]++; 149 | 150 | require(block.timestamp <= expiry, "NBT::delegateBySig: signature expired"); 151 | _delegate(signatory, delegatee); 152 | } 153 | 154 | /** 155 | * @notice Gets the current votes balance for `account` 156 | * @param account The address to get votes balance 157 | * @return The number of current votes for `account` 158 | */ 159 | function getCurrentVotes(address account) 160 | external 161 | view 162 | returns (uint256) 163 | { 164 | uint32 nCheckpoints = numCheckpoints[account]; 165 | return nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0; 166 | } 167 | 168 | /** 169 | * @notice Determine the prior number of votes for an account as of a block number 170 | * @dev Block number must be a finalized block or else this function will revert to prevent misinformation. 171 | * @param account The address of the account to check 172 | * @param blockNumber The block number to get the vote balance at 173 | * @return The number of votes the account had as of the given block 174 | */ 175 | function getPriorVotes(address account, uint blockNumber) 176 | external 177 | view 178 | returns (uint256) 179 | { 180 | require(blockNumber < block.number, "NBT::getPriorVotes: not yet determined"); 181 | 182 | uint32 nCheckpoints = numCheckpoints[account]; 183 | if (nCheckpoints == 0) { 184 | return 0; 185 | } 186 | 187 | // First check most recent balance 188 | if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) { 189 | return checkpoints[account][nCheckpoints - 1].votes; 190 | } 191 | 192 | // Next check implicit zero balance 193 | if (checkpoints[account][0].fromBlock > blockNumber) { 194 | return 0; 195 | } 196 | 197 | uint32 lower = 0; 198 | uint32 upper = nCheckpoints - 1; 199 | while (upper > lower) { 200 | uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow 201 | Checkpoint memory cp = checkpoints[account][center]; 202 | if (cp.fromBlock == blockNumber) { 203 | return cp.votes; 204 | } else if (cp.fromBlock < blockNumber) { 205 | lower = center; 206 | } else { 207 | upper = center - 1; 208 | } 209 | } 210 | return checkpoints[account][lower].votes; 211 | } 212 | 213 | function _delegate(address delegator, address delegatee) 214 | internal 215 | { 216 | address currentDelegate = _delegates[delegator]; 217 | uint256 delegatorBalance = balanceOf(delegator); // balance of underlying NBTs (not scaled); 218 | _delegates[delegator] = delegatee; 219 | 220 | emit DelegateChanged(delegator, currentDelegate, delegatee); 221 | 222 | _moveDelegates(currentDelegate, delegatee, delegatorBalance); 223 | } 224 | 225 | function _moveDelegates(address srcRep, address dstRep, uint256 amount) internal { 226 | if (srcRep != dstRep && amount > 0) { 227 | if (srcRep != address(0)) { 228 | // decrease old representative 229 | uint32 srcRepNum = numCheckpoints[srcRep]; 230 | uint256 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0; 231 | uint256 srcRepNew = srcRepOld.sub(amount); 232 | _writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew); 233 | } 234 | 235 | if (dstRep != address(0)) { 236 | // increase new representative 237 | uint32 dstRepNum = numCheckpoints[dstRep]; 238 | uint256 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0; 239 | uint256 dstRepNew = dstRepOld.add(amount); 240 | _writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew); 241 | } 242 | } 243 | } 244 | 245 | function _writeCheckpoint( 246 | address delegatee, 247 | uint32 nCheckpoints, 248 | uint256 oldVotes, 249 | uint256 newVotes 250 | ) 251 | internal 252 | { 253 | uint32 blockNumber = safe32(block.number, "NBT::_writeCheckpoint: block number exceeds 32 bits"); 254 | 255 | if (nCheckpoints > 0 && checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber) { 256 | checkpoints[delegatee][nCheckpoints - 1].votes = newVotes; 257 | } else { 258 | checkpoints[delegatee][nCheckpoints] = Checkpoint(blockNumber, newVotes); 259 | numCheckpoints[delegatee] = nCheckpoints + 1; 260 | } 261 | 262 | emit DelegateVotesChanged(delegatee, oldVotes, newVotes); 263 | } 264 | 265 | function safe32(uint n, string memory errorMessage) internal pure returns (uint32) { 266 | require(n < 2**32, errorMessage); 267 | return uint32(n); 268 | } 269 | 270 | function getChainId() internal view returns (uint) { 271 | uint256 chainId; 272 | assembly { chainId := chainid() } 273 | return chainId; 274 | } 275 | } -------------------------------------------------------------------------------- /contracts/libs/BEP20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import '@openzeppelin/contracts/access/Ownable.sol'; 6 | import '@openzeppelin/contracts/utils/Context.sol'; 7 | import './IBEP20.sol'; 8 | import '@openzeppelin/contracts/utils/math/SafeMath.sol'; 9 | 10 | /** 11 | * @dev Implementation of the {IBEP20} interface. 12 | * 13 | * This implementation is agnostic to the way tokens are created. This means 14 | * that a supply mechanism has to be added in a derived contract using {_mint}. 15 | * For a generic mechanism see {BEP20PresetMinterPauser}. 16 | * 17 | * TIP: For a detailed writeup see our guide 18 | * https://forum.zeppelin.solutions/t/how-to-implement-BEP20-supply-mechanisms/226[How 19 | * to implement supply mechanisms]. 20 | * 21 | * We have followed general OpenZeppelin guidelines: functions revert instead 22 | * of returning `false` on failure. This behavior is nonetheless conventional 23 | * and does not conflict with the expectations of BEP20 applications. 24 | * 25 | * Additionally, an {Approval} event is emitted on calls to {transferFrom}. 26 | * This allows applications to reconstruct the allowance for all accounts just 27 | * by listening to said events. Other implementations of the EIP may not emit 28 | * these events, as it isn't required by the specification. 29 | * 30 | * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} 31 | * functions have been added to mitigate the well-known issues around setting 32 | * allowances. See {IBEP20-approve}. 33 | */ 34 | contract BEP20 is Context, IBEP20, Ownable { 35 | using SafeMath for uint256; 36 | 37 | mapping(address => uint256) private _balances; 38 | 39 | mapping(address => mapping(address => uint256)) private _allowances; 40 | 41 | uint256 private _totalSupply; 42 | 43 | string private _name; 44 | string private _symbol; 45 | uint8 private _decimals; 46 | 47 | /** 48 | * @dev Sets the values for {name} and {symbol}, initializes {decimals} with 49 | * a default value of 18. 50 | * 51 | * To select a different value for {decimals}, use {_setupDecimals}. 52 | * 53 | * All three of these values are immutable: they can only be set once during 54 | * construction. 55 | */ 56 | constructor(string memory name, string memory symbol) { 57 | _name = name; 58 | _symbol = symbol; 59 | _decimals = 18; 60 | } 61 | 62 | /** 63 | * @dev Returns the bep token owner. 64 | */ 65 | function getOwner() external override view returns (address) { 66 | return owner(); 67 | } 68 | 69 | /** 70 | * @dev Returns the name of the token. 71 | */ 72 | function name() public override view returns (string memory) { 73 | return _name; 74 | } 75 | 76 | /** 77 | * @dev Returns the symbol of the token, usually a shorter version of the 78 | * name. 79 | */ 80 | function symbol() public override view returns (string memory) { 81 | return _symbol; 82 | } 83 | 84 | /** 85 | * @dev Returns the number of decimals used to get its user representation. 86 | */ 87 | function decimals() public override view returns (uint8) { 88 | return _decimals; 89 | } 90 | 91 | /** 92 | * @dev See {BEP20-totalSupply}. 93 | */ 94 | function totalSupply() public override view returns (uint256) { 95 | return _totalSupply; 96 | } 97 | 98 | /** 99 | * @dev See {BEP20-balanceOf}. 100 | */ 101 | function balanceOf(address account) public override view returns (uint256) { 102 | return _balances[account]; 103 | } 104 | 105 | /** 106 | * @dev See {BEP20-transfer}. 107 | * 108 | * Requirements: 109 | * 110 | * - `recipient` cannot be the zero address. 111 | * - the caller must have a balance of at least `amount`. 112 | */ 113 | function transfer(address recipient, uint256 amount) public override returns (bool) { 114 | _transfer(_msgSender(), recipient, amount); 115 | return true; 116 | } 117 | 118 | /** 119 | * @dev See {BEP20-allowance}. 120 | */ 121 | function allowance(address owner, address spender) public override view returns (uint256) { 122 | return _allowances[owner][spender]; 123 | } 124 | 125 | /** 126 | * @dev See {BEP20-approve}. 127 | * 128 | * Requirements: 129 | * 130 | * - `spender` cannot be the zero address. 131 | */ 132 | function approve(address spender, uint256 amount) public override returns (bool) { 133 | _approve(_msgSender(), spender, amount); 134 | return true; 135 | } 136 | 137 | /** 138 | * @dev See {BEP20-transferFrom}. 139 | * 140 | * Emits an {Approval} event indicating the updated allowance. This is not 141 | * required by the EIP. See the note at the beginning of {BEP20}; 142 | * 143 | * Requirements: 144 | * - `sender` and `recipient` cannot be the zero address. 145 | * - `sender` must have a balance of at least `amount`. 146 | * - the caller must have allowance for `sender`'s tokens of at least 147 | * `amount`. 148 | */ 149 | function transferFrom (address sender, address recipient, uint256 amount) public override returns (bool) { 150 | _transfer(sender, recipient, amount); 151 | _approve( 152 | sender, 153 | _msgSender(), 154 | _allowances[sender][_msgSender()].sub(amount, 'BEP20: transfer amount exceeds allowance') 155 | ); 156 | return true; 157 | } 158 | 159 | /** 160 | * @dev Atomically increases the allowance granted to `spender` by the caller. 161 | * 162 | * This is an alternative to {approve} that can be used as a mitigation for 163 | * problems described in {BEP20-approve}. 164 | * 165 | * Emits an {Approval} event indicating the updated allowance. 166 | * 167 | * Requirements: 168 | * 169 | * - `spender` cannot be the zero address. 170 | */ 171 | function increaseAllowance(address spender, uint256 addedValue) public returns (bool) { 172 | _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue)); 173 | return true; 174 | } 175 | 176 | /** 177 | * @dev Atomically decreases the allowance granted to `spender` by the caller. 178 | * 179 | * This is an alternative to {approve} that can be used as a mitigation for 180 | * problems described in {BEP20-approve}. 181 | * 182 | * Emits an {Approval} event indicating the updated allowance. 183 | * 184 | * Requirements: 185 | * 186 | * - `spender` cannot be the zero address. 187 | * - `spender` must have allowance for the caller of at least 188 | * `subtractedValue`. 189 | */ 190 | function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) { 191 | _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, 'BEP20: decreased allowance below zero')); 192 | return true; 193 | } 194 | 195 | /** 196 | * @dev Creates `amount` tokens and assigns them to `msg.sender`, increasing 197 | * the total supply. 198 | * 199 | * Requirements 200 | * 201 | * - `msg.sender` must be the token owner 202 | */ 203 | function mint(uint256 amount) external virtual onlyOwner returns (bool) { 204 | _mint(_msgSender(), amount); 205 | return true; 206 | } 207 | 208 | /** 209 | * @dev Moves tokens `amount` from `sender` to `recipient`. 210 | * 211 | * This is internal function is equivalent to {transfer}, and can be used to 212 | * e.g. implement automatic token fees, slashing mechanisms, etc. 213 | * 214 | * Emits a {Transfer} event. 215 | * 216 | * Requirements: 217 | * 218 | * - `sender` cannot be the zero address. 219 | * - `recipient` cannot be the zero address. 220 | * - `sender` must have a balance of at least `amount`. 221 | */ 222 | function _transfer (address sender, address recipient, uint256 amount) internal virtual { 223 | require(sender != address(0), 'BEP20: transfer from the zero address'); 224 | require(recipient != address(0), 'BEP20: transfer to the zero address'); 225 | 226 | _balances[sender] = _balances[sender].sub(amount, 'BEP20: transfer amount exceeds balance'); 227 | _balances[recipient] = _balances[recipient].add(amount); 228 | emit Transfer(sender, recipient, amount); 229 | } 230 | 231 | /** @dev Creates `amount` tokens and assigns them to `account`, increasing 232 | * the total supply. 233 | * 234 | * Emits a {Transfer} event with `from` set to the zero address. 235 | * 236 | * Requirements 237 | * 238 | * - `to` cannot be the zero address. 239 | */ 240 | function _mint(address account, uint256 amount) internal virtual { 241 | require(account != address(0), 'BEP20: mint to the zero address'); 242 | 243 | _totalSupply = _totalSupply.add(amount); 244 | _balances[account] = _balances[account].add(amount); 245 | emit Transfer(address(0), account, amount); 246 | } 247 | 248 | /** 249 | * @dev Destroys `amount` tokens from `account`, reducing the 250 | * total supply. 251 | * 252 | * Emits a {Transfer} event with `to` set to the zero address. 253 | * 254 | * Requirements 255 | * 256 | * - `account` cannot be the zero address. 257 | * - `account` must have at least `amount` tokens. 258 | */ 259 | function _burn(address account, uint256 amount) internal { 260 | require(account != address(0), 'BEP20: burn from the zero address'); 261 | 262 | _balances[account] = _balances[account].sub(amount, 'BEP20: burn amount exceeds balance'); 263 | _totalSupply = _totalSupply.sub(amount); 264 | emit Transfer(account, address(0), amount); 265 | } 266 | 267 | /** 268 | * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens. 269 | * 270 | * This is internal function is equivalent to `approve`, and can be used to 271 | * e.g. set automatic allowances for certain subsystems, etc. 272 | * 273 | * Emits an {Approval} event. 274 | * 275 | * Requirements: 276 | * 277 | * - `owner` cannot be the zero address. 278 | * - `spender` cannot be the zero address. 279 | */ 280 | function _approve (address owner, address spender, uint256 amount) internal { 281 | require(owner != address(0), 'BEP20: approve from the zero address'); 282 | require(spender != address(0), 'BEP20: approve to the zero address'); 283 | 284 | _allowances[owner][spender] = amount; 285 | emit Approval(owner, spender, amount); 286 | } 287 | 288 | /** 289 | * @dev Destroys `amount` tokens from `account`.`amount` is then deducted 290 | * from the caller's allowance. 291 | * 292 | * See {_burn} and {_approve}. 293 | */ 294 | function _burnFrom(address account, uint256 amount) internal { 295 | _burn(account, amount); 296 | _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, 'BEP20: burn amount exceeds allowance')); 297 | } 298 | } --------------------------------------------------------------------------------