Initial commit

This commit is contained in:
Daniel Perez 2023-02-13 01:06:14 +00:00
commit 8a9462b868
No known key found for this signature in database
GPG key ID: A0B4CCFEBD33E18A
12 changed files with 821 additions and 0 deletions

14
.gitignore vendored Normal file
View file

@ -0,0 +1,14 @@
# Compiler files
cache/
out/
# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/
# Docs
docs/
# Dotenv file
.env

4
.gitmodules vendored Normal file
View file

@ -0,0 +1,4 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
branch = v1.3.0

6
README.md Normal file
View file

@ -0,0 +1,6 @@
# Coursework skeleton
This is the skeleton for the coursework of the Principle of Distributed Ledgers 2023.
It contains [the interfaces](./src/interfaces) of the contracts to implement and an [ERC20 implementation](./src/contracts/PurchaseToken.sol).
The repository uses [Foundry](https://book.getfoundry.sh/projects/working-on-an-existing-project).

7
foundry.toml Normal file
View file

@ -0,0 +1,7 @@
[profile.default]
src = 'src'
out = 'out'
libs = ['lib']
solc = "0.8.10"
# See more config options https://github.com/foundry-rs/foundry/tree/master/config

1
lib/forge-std Submodule

@ -0,0 +1 @@
Subproject commit 066ff16c5c03e6f931cd041fd366bc4be1fae82a

0
lib/lib.sol Normal file
View file

View file

@ -0,0 +1,451 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.10;
import "../interfaces/IERC20.sol";
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract PurchaseToken is Context, IERC20, IERC20Metadata {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor() {
_name = "PurchaseToken";
_symbol = "PT";
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `18`, a balance of `5e18` tokens should
* be displayed to a user as `5.00` (`5e18 / 10 ** 18`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
*/
function decimals() public view virtual override returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account)
public
view
virtual
override
returns (uint256)
{
return _balances[account];
}
/**
* @dev This allows users to mint ERC20 tokens by sending ETH to this contract.
* The amount of ERC20 tokens minted will be 100 times the amount of ETH sent.
*/
function mint() external payable {
uint256 amountToMint = msg.value * 100;
_mint(msg.sender, amountToMint);
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address to, uint256 amount)
public
virtual
override
returns (bool)
{
address owner = _msgSender();
_transfer(owner, to, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender)
public
view
virtual
override
returns (uint256)
{
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount)
public
virtual
override
returns (bool)
{
address owner = _msgSender();
_approve(owner, spender, amount);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
* - the caller must have allowance for ``from``'s tokens of at least
* `amount`.
*/
function transferFrom(
address from,
address to,
uint256 amount
) public virtual override returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue)
public
virtual
returns (bool)
{
address owner = _msgSender();
_approve(owner, spender, allowance(owner, spender) + addedValue);
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue)
public
virtual
returns (bool)
{
address owner = _msgSender();
uint256 currentAllowance = allowance(owner, spender);
require(
currentAllowance >= subtractedValue,
"ERC20: decreased allowance below zero"
);
unchecked {
_approve(owner, spender, currentAllowance - subtractedValue);
}
return true;
}
/**
* @dev Moves `amount` of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
*/
function _transfer(
address from,
address to,
uint256 amount
) internal virtual {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(from, to, amount);
uint256 fromBalance = _balances[from];
require(
fromBalance >= amount,
"ERC20: transfer amount exceeds balance"
);
unchecked {
_balances[from] = fromBalance - amount;
// Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
// decrementing then incrementing.
_balances[to] += amount;
}
emit Transfer(from, to, amount);
_afterTokenTransfer(from, to, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
unchecked {
// Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
_balances[account] += amount;
}
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
// Overflow not possible: amount <= accountBalance <= totalSupply.
_totalSupply -= amount;
}
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(
address owner,
address spender,
uint256 amount
) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `amount`.
*
* Does not update the allowance amount in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Might emit an {Approval} event.
*/
function _spendAllowance(
address owner,
address spender,
uint256 amount
) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(
currentAllowance >= amount,
"ERC20: insufficient allowance"
);
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
/**
* @dev Hook that is called after any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* has been transferred to `to`.
* - when `from` is zero, `amount` tokens have been minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens have been burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
}

106
src/interfaces/IERC20.sol Normal file
View file

@ -0,0 +1,106 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.10;
/**
* @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);
}
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/extensions/IERC20Metadata.sol)
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}

View file

@ -0,0 +1,31 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;
/**
* @dev Required interface for the primary market.
* The primary market is the first point of sale for tickets.
* It is responsible for minting tickets and transferring them to the purchaser.
* In this implementation, the purchase price is fixed at 100e18 purchase tokens
* and the maximum number of tickets that can be purchased is 1000.
* The purchase token is an ERC20 token that is specified when the contract is deployed.
* The NFT to be minted is an implementation of the ITicketNFT interface and should be created (i.e. deployed)
* when the contract implementing this interface is deployed.
*/
interface IPrimaryMarket {
/**
* @dev Emitted when a purchase by `holder` occurs, with `holderName` specified.
*/
event Purchase(address indexed holder, string indexed holderName);
/**
* @dev Returns the administrator of the primary market.
* This should be the address that created the contract.
*/
function admin() external view returns (address);
/**
* @dev Takes the initial NFT token holder's name as a string input
* and transfers ERC20 tokens from the purchaser to the admin of this contract
*/
function purchase(string memory holderName) external;
}

View file

@ -0,0 +1,55 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;
/**
* @dev Required interface for the secondary market.
* The secondary market is the point of sale for tickets after they have been initially purchased from the primary market
*/
interface ISecondaryMarket {
/**
* @dev Event emitted when a new ticket listing is created
*/
event Listing(
uint256 indexed ticketID,
address indexed holder,
uint256 price
);
/**
* @dev Event emitted when an amount of the purchase token is transferred from
*/
event Purchase(
address indexed purchaser,
uint256 indexed ticketID,
uint256 price,
string newName
);
/**
* @dev Event emitted when a ticket is delisted
*/
event Delisting(uint256 indexed ticketID);
/**
* @dev This method lists a ticket with `ticketID` for sale by transferring the ticket
* such that it is held by this contract. Only the current owner of a specific
* ticket is able to list that ticket on the secondary market. The purchase
* `price` is specified in an amount of `PurchaseToken`.
*/
function listTicket(uint256 ticketID, uint256 price) external;
/** @dev This method allows the msg.sender to purchase a listed ticket with `ticketID`
* by paying the purchase price that was specified when the ticket was listed.
* `name` gives the new name that should be stated on the ticket when it is purchased.
* Note: Only non-expired and unused tickets can be purchased and there is a
* fee charged every time a purchase is made. The fee is charged on the price.
* The final amount that the lister of the ticket receives is the price
* minus the fee. The fee should go to the admin of the primary market.
*/
function purchase(uint256 ticketID, string calldata name) external;
/** @dev This method delists a previously listed ticket with `ticketID`. Only the account that
* listed the ticket may delist the ticket. The ticket should be transferred back
* to msg.sender, i.e., the lister.
*/
function delistTicket(uint256 ticketID) external;
}

View file

@ -0,0 +1,146 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;
/**
* @dev Required interface for the TicketNFT contract.
* A ticket NFT is a non-fungible token that represents a single entry to an event.
*/
interface ITicketNFT {
/**
* @dev Emitted when `ticketID` ticket is transferred from `from` to `to`.
*/
event Transfer(
address indexed from,
address indexed to,
uint256 indexed ticketID
);
/**
* @dev Emitted when `holder` enables `approved` to manage the `ticketID` ticket.
*/
event Approval(
address indexed holder,
address indexed approved,
uint256 indexed ticketID
);
/**
* Mints a new ticket for `holder` with `holderName`.
* The ticket must be assigned the following metadata:
* - A unique ticket ID. Once a ticket has been used or expired, its ID should not be reallocated
* - An expiry time of 10 days from the time of minting
* - A boolean `used` flag set to false
* On minting, a `Transfer` event should be emitted with `from` set to the zero address.
*
* Requirements:
*
* - The caller must be the primary market
*/
function mint(address holder, string memory holderName) external;
/**
* @dev Returns the number of tickets a `holder` has.
*/
function balanceOf(address holder) external view returns (uint256 balance);
/**
* @dev Returns the address of the holder of the `ticketID` ticket.
*
* Requirements:
*
* - `ticketID` must exist.
*/
function holderOf(uint256 ticketID) external view returns (address holder);
/**
* @dev Transfers `ticketID` ticket from `from` to `to`.
* This should also set the approved address for this ticket to the zero address
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `ticketID` ticket must be either:
* - owned by `from`.
* - approved to move this ticket by `approve`
*
* Emits a `Transfer` and an `Approval` event.
*/
function transferFrom(
address from,
address to,
uint256 ticketID
) external;
/**
* @dev Gives permission to `to` to transfer `ticketID` ticket to another account.
* The approval is cleared when the ticket is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the ticket
* - `ticketID` must exist.
*
* Emits an `Approval` event.
*/
function approve(address to, uint256 ticketID) external;
/**
* @dev Returns the account approved for `ticketID` ticket.
*
* Requirements:
*
* - `ticketID` must exist.
*/
function getApproved(uint256 ticketID)
external
view
returns (address operator);
/**
* @dev Returns the current `holderName` associated with a `ticketID`.
* Requirements:
*
* - `ticketID` must exist.
*/
function holderNameOf(uint256 ticketID)
external
view
returns (string memory holderName);
/**
* @dev Updates the `holderName` associated with a `ticketID`.
* Note that this does not update the actual holder of the ticket.
*
* Requirements:
*
* - `ticketID` must exists
* - Only the current holder can call this function
*/
function updateHolderName(uint256 ticketID, string calldata newName)
external;
/**
* @dev Sets the `used` flag associated with a `ticketID` to `true`
*
* Requirements:
*
* - `ticketID` must exist
* - the ticket must not already be used
* - the ticket must not be expired
* - Only the administrator of the primary market can call this function
*/
function setUsed(uint256 ticketID) external;
/**
* @dev Returns `true` if the `used` flag associated with a `ticketID` if `true`
* or if the ticket has expired, i.e., the current time is greater than the ticket's
* `expiryDate`.
* Requirements:
*
* - `ticketID` must exist
*/
function isExpiredOrUsed(uint256 ticketID) external view returns (bool);
}

0
test/.keep Normal file
View file