diff --git a/bond_issuance_py/.env b/bond_issuance_py/.env new file mode 100644 index 000000000..952834225 --- /dev/null +++ b/bond_issuance_py/.env @@ -0,0 +1,3 @@ +# SCRIPT ENV VARIABLES +export HOST= + diff --git a/bond_issuance_py/bond_poc.py b/bond_issuance_py/bond_poc.py new file mode 100644 index 000000000..339e3887d --- /dev/null +++ b/bond_issuance_py/bond_poc.py @@ -0,0 +1,467 @@ +""" +PLease set following environment variables for this script to work properly + +export HOST= default value=None +""" +import json +import subprocess +import time + +import urllib3 +from solcx import compile_files, install_solc +from web3 import Web3 +from web3._utils.events import get_event_data +from web3.middleware import geth_poa_middleware + +urllib3.disable_warnings() + +with open('./resources/accounts.json') as json_file: + accounts = json.load(json_file) + + exchange_addr = accounts["exchange"] + exchange_key = accounts["exchange_key"] + + treasury_addr = accounts["treasury"] + treasury_key = accounts["treasury_key"] + + mm_addr = accounts["mm"] + mm_key = accounts["mm_keys"] + + +def setup_w3(host): + """ + setup w3 http provider + """ + url = "http://" + host + ":8545" + global w3 + w3 = Web3(Web3.HTTPProvider(url, request_kwargs={"verify": False})) + w3.middleware_onion.inject(geth_poa_middleware, layer=0) + + +def compile_exchange(): + """ + compiling Exchange source file + """ + install_solc("0.8.9") + # global exchange_abi, exchange_bytecode + compiled_sol = compile_files( + ["contracts/Exchange.sol"], solc_version='0.8.9', optimize=True) + exchange_bytecode = compiled_sol['contracts/Exchange.sol:Exchange']['bin'] + exchange_abi = compiled_sol['contracts/Exchange.sol:Exchange']['abi'] + return exchange_bytecode, exchange_abi + + +def tx_receipt_poll(construct_txn, acc_priv_key): + """ + function to validate tx hash and poll for receipt + Arguments: + construct_txn: transaction construct + acc_priv_key: private key of account + """ + signed_txn = w3.eth.account.sign_transaction( + construct_txn, acc_priv_key) + # Validating transaction hash + tx_hash_send = signed_txn.hash + tx_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction) + assert tx_hash_send == tx_hash, "tx hash mismatch" + + tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash) + assert tx_receipt.status == 1, "transaction failed" + return tx_receipt + + +def deploy_exchange(pt_name, pt_symbol): + """ + deploy exchange contract and distribute tokens among all senders + Arguments: + pt_name: Payment token Name + pt_symbol: symbol for Payment token + """ + exchange_bytecode, exchange_abi = compile_exchange() + contract = w3.eth.contract(abi=exchange_abi, bytecode=exchange_bytecode) + + # deploying contract + construct_txn = contract.constructor(pt_name, pt_symbol).buildTransaction( + { + "from": exchange_addr, + "gas": 2000000, + "gasPrice": 0, + "nonce": w3.eth.get_transaction_count(exchange_addr), + "chainId": 5000, + } + ) + tx_receipt = tx_receipt_poll(construct_txn, exchange_key) + print("\nExchange smart contract deploy success, contract address: '{}'".format( + tx_receipt.contractAddress)) + + exchange_contract_address = tx_receipt.contractAddress + with open('./resources/contracts.json','r') as file_read: + data = json.load(file_read) + data["exchange_contract_address"] = exchange_contract_address + with open('./resources/contracts.json', 'r+') as file_write: + json.dump(data,file_write, indent=4) + + +def set_pt(): + """ + get payment token address and add it to json + """ + exchange_contract, exchange_contract_address = get_exchange_contract() + exchange_bytecode, exchange_abi = compile_exchange() + exchange_contract = w3.eth.contract(address=exchange_contract_address, abi=exchange_abi) + pt_address = exchange_contract.caller({'from': exchange_addr}).getPtAddress() + print("PT address - {}\n".format(pt_address)) + with open('./resources/contracts.json','r') as file_read: + data = json.load(file_read) + data["pt_address"] = pt_address + with open('./resources/contracts.json', 'r+') as file_write: + json.dump(data,file_write, indent=4) + + +def mint_pt(mm_num, pt_val): + """ + Mint payment token for a sender + Arguments: + mm_num: index for Market maker + pt_val: payment token amount to be minted + """ + with open('./resources/contracts.json','r') as file_read: + data = json.load(file_read) + exchange_contract_address = data["exchange_contract_address"] + exchange_bytecode, exchange_abi = compile_exchange() + exchange_contract = w3.eth.contract(address=exchange_contract_address, abi=exchange_abi) + construct_txn = exchange_contract.functions.mintAndTransferPT(pt_val, mm_addr[mm_num]).buildTransaction({ + 'from': exchange_addr, + 'gas': 2000000, + 'gasPrice': 0, + 'nonce': w3.eth.get_transaction_count(exchange_addr), + 'chainId': 5000 + }) + tx_receipt_poll(construct_txn, exchange_key) + balance = exchange_contract.caller({'from': exchange_addr}).get_balance_pt(mm_addr[mm_num]) + print("MM{} ({}) got balance - {} PT".format(mm_num+1, mm_addr[mm_num], balance)) + + +def mint_st(): + """ + Mint security token + """ + exchange_contract,exchange_contract_address = get_exchange_contract() + with open('./resources/security-tokens.json') as json_file: + stks = json.load(json_file)["tokens"] + for stk in stks: + construct_txn = exchange_contract.functions.mintAndTransferST(stk["isin"], treasury_addr, + stk["interest_rate"], stk["cost_in_pt"], + stk["maturity_date"], + stk["bond_amount"]).buildTransaction({ + 'from': exchange_addr, + 'gas': 2000000, + 'gasPrice': 0, + 'nonce': w3.eth.get_transaction_count(exchange_addr), + 'chainId': 5000 + }) + tx_receipt_poll(construct_txn, exchange_key) + balance = exchange_contract.caller({'from': exchange_addr}).get_balance_st(treasury_addr, stk["isin"]) + print("Treasury got balance - {} ST for ISIN - {}".format(balance, stk["isin"])) + + +def swap_token_start(mm_num, st_val, st_isin): + """ + call swapTokens method from exchange + Arguments: + mm_num: market maker number + st_val: security token amount + st_isin: ISIN of security token + """ + exchange_contract, exchange_contract_address = get_exchange_contract() + construct_txn = exchange_contract.functions.swapTokens(mm_addr[mm_num], treasury_addr, st_isin, st_val).buildTransaction({ + 'from': exchange_addr, + 'gas': 2000000, + 'gasPrice': 0, + 'nonce': w3.eth.get_transaction_count(exchange_addr), + 'chainId': 5000 + }) + tx_receipt_poll(construct_txn, exchange_key) + + +def increase_allowance_pt(mm_num, allowance_val): + """ + Increase allowance for Payment token for exchange + Arguments: + mm_num: Market maker index + allowance_val: allowance amount + """ + with open('./resources/contracts.json','r') as file_read: + data = json.load(file_read) + exchange_contract_address = data["exchange_contract_address"] + pt_address = data["pt_address"] + compiled_sol = compile_files( + ["contracts/PaymentToken.sol"], solc_version='0.8.9', optimize=True) + pt_abi = compiled_sol['contracts/PaymentToken.sol:PaymentToken']['abi'] + pt_contract = w3.eth.contract(address=pt_address, abi=pt_abi) + construct_txn = pt_contract.functions.increaseAllowance(exchange_contract_address, allowance_val).buildTransaction({ + 'from': mm_addr[mm_num], + 'gas': 2000000, + 'gasPrice': 0, + 'nonce': w3.eth.get_transaction_count(mm_addr[mm_num]), + 'chainId': 5000 + }) + tx_receipt_poll(construct_txn, mm_key[mm_num]) + allowance = pt_contract.caller({'from': mm_addr[mm_num]}).allowance(mm_addr[mm_num], exchange_contract_address) + print("\nAllowance of Exchange is - {} PT".format(allowance)) + + +def decrease_allowance_pt(mm_num, allowance_val): + """ + Decrease allowance for Payment token for exchange + Arguments: + mm_num: Market maker index + allowance_val: allowance amount + """ + with open('./resources/contracts.json','r') as file_read: + data = json.load(file_read) + exchange_contract_address = data["exchange_contract_address"] + pt_address = data["pt_address"] + compiled_sol = compile_files( + ["contracts/PaymentToken.sol"], solc_version='0.8.9', optimize=True) + pt_abi = compiled_sol['contracts/PaymentToken.sol:PaymentToken']['abi'] + pt_contract = w3.eth.contract(address=pt_address, abi=pt_abi) + construct_txn = pt_contract.functions.decreaseAllowance(exchange_contract_address, allowance_val).buildTransaction({ + 'from': mm_addr[mm_num], + 'gas': 2000000, + 'gasPrice': 0, + 'nonce': w3.eth.get_transaction_count(mm_addr[mm_num]), + 'chainId': 5000 + }) + tx_receipt_poll(construct_txn, mm_key[mm_num]) + allowance = pt_contract.caller({'from': mm_addr[mm_num]}).allowance(mm_addr[mm_num], exchange_contract_address) + print("\nAllowance of Exchange is - {} PT".format(allowance)) + + +def increase_allowance_st(st_isin, allowance_val): + """ + Increase allowance for Security token for exchange + Arguments: + st_isin: ISIN for security token + allowance_val: allowance amount + """ + exchange_contract, exchange_contract_address = get_exchange_contract() + st_address = exchange_contract.caller({'from': exchange_addr}).getStAddress(st_isin) + compiled_sol = compile_files( + ["contracts/SecurityToken.sol"], solc_version='0.8.9', optimize=True) + st_abi = compiled_sol['contracts/SecurityToken.sol:SecurityToken']['abi'] + st_contract = w3.eth.contract(address=st_address, abi=st_abi) + + construct_txn = st_contract.functions.increaseAllowance(exchange_contract_address, allowance_val).buildTransaction({ + 'from': treasury_addr, + 'gas': 2000000, + 'gasPrice': 0, + 'nonce': w3.eth.get_transaction_count(treasury_addr), + 'chainId': 5000 + }) + tx_receipt_poll(construct_txn, treasury_key) + allowance = st_contract.caller({'from': treasury_addr}).allowance(treasury_addr, exchange_contract_address) + print("\nAllowance of Exchange is - {} ST ({})".format(allowance, st_isin)) + + +def decrease_allowance_st(st_isin, allowance_val): + """ + Decrease allowance for Security token for exchange + Arguments: + st_isin: ISIN for security token + allowance_val: allowance amount + """ + exchange_contract, exchange_contract_address = get_exchange_contract() + st_address = exchange_contract.caller({'from': exchange_addr}).getStAddress(st_isin) + compiled_sol = compile_files( + ["contracts/SecurityToken.sol"], solc_version='0.8.9', optimize=True) + st_abi = compiled_sol['contracts/SecurityToken.sol:SecurityToken']['abi'] + st_contract = w3.eth.contract(address=st_address, abi=st_abi) + + construct_txn = st_contract.functions.decreaseAllowance(exchange_contract_address, allowance_val).buildTransaction({ + 'from': treasury_addr, + 'gas': 2000000, + 'gasPrice': 0, + 'nonce': w3.eth.get_transaction_count(treasury_addr), + 'chainId': 5000 + }) + tx_receipt_poll(construct_txn, treasury_key) + allowance = st_contract.caller({'from': treasury_addr}).allowance(treasury_addr, exchange_contract_address) + print("\nAllowance of Exchange is - {} ST ({})".format(allowance, st_isin)) + + +def swap_token_complete(transaction_num, st_isin, mm_num): + """ + call _swapTokens to complete swap + Arguments: + transaction_num: transaction number + st_isin: ISIN for security Token + mm_num: market maker index + """ + exchange_contract, exchange_contract_address = get_exchange_contract() + construct_txn = exchange_contract.functions._swapTokens(transaction_num).buildTransaction({ + 'from': exchange_addr, + 'gas': 2000000, + 'gasPrice': 0, + 'nonce': w3.eth.get_transaction_count(exchange_addr), + 'chainId': 5000 + }) + tx_receipt_poll(construct_txn, exchange_key) + balancePT = exchange_contract.caller({'from': exchange_addr}).get_balance_pt(treasury_addr) + print("\nTreasury got balance - {} PT".format(balancePT)) + balanceST = exchange_contract.caller({'from': exchange_addr}).get_balance_st(mm_addr[mm_num], st_isin) + print("MM{} ({}) got balance - {} ST ({})".format(mm_num+1, mm_addr[mm_num], balanceST, st_isin)) + + +def close_issuance(): + """ + close issuance and print all balances + burn the ST remaining with treasury at the end issuance + """ + json_file = open('./resources/security-tokens.json') + stks = json.load(json_file)["tokens"] + print("\nbalance for treasury") + exchange_contract, exchange_contract_address = get_exchange_contract() + + print("PT balance for treasury : {} ".format(exchange_contract.caller({'from': exchange_addr}).get_balance_pt(treasury_addr))) + for stk in stks: + print("isin : {}".format(stk["isin"])) + st_balance_issuance = exchange_contract.caller({'from': exchange_addr}).get_balance_st(treasury_addr, stk["isin"]) + print("ST balance for treasury before burn: {} ".format(st_balance_issuance)) + burn_st(st_balance_issuance, stk["isin"]) + st_balance_issuance = exchange_contract.caller({'from': exchange_addr}).get_balance_st(treasury_addr, stk["isin"]) + print("ST balance for treasury after burn: {} ".format(st_balance_issuance)) + print("\nbalance for MMs") + index = 0 + for mm in mm_addr: + index = index + 1 + print("\nPT balance for mm {} : {} ".format(index, exchange_contract.caller({'from': exchange_addr}).get_balance_pt(mm))) + for st in stks: + print("isin : {}".format(st["isin"])) + print("ST balance for mm {}: {} ".format(index, exchange_contract.caller({'from': exchange_addr}).get_balance_st(mm, st["isin"]))) + + +def burn_st(amount, st_isin): + """ + burn security token for an ST + Arguments: + amount: amount of st token to be burned + st_isin: isin value + Return: + None + """ + exchange_contract, exchange_contract_address = get_exchange_contract() + st_address = exchange_contract.caller({'from': exchange_addr}).getStAddress(st_isin) + compiled_sol = compile_files( + ["contracts/SecurityToken.sol"], solc_version='0.8.9', optimize=True) + st_abi = compiled_sol['contracts/SecurityToken.sol:SecurityToken']['abi'] + st_contract = w3.eth.contract(address=st_address, abi=st_abi) + + construct_txn = st_contract.functions.burn(amount).buildTransaction({ + 'from': treasury_addr, + 'gas': 2000000, + 'gasPrice': 0, + 'nonce': w3.eth.get_transaction_count(treasury_addr), + 'chainId': 5000 + }) + tx_receipt_poll(construct_txn, treasury_key) + + +def handle_event(event, event_template): + """ + get event data and decode it + Arguments: + event: event + event_template: event template(object representing event) + """ + try: + result = get_event_data(event_template.web3.codec, event_template._get_event_abi(), event) + return True, result + except: + return False, None + + +def swap_tokens(mm_num, st_val, st_isin, delay=False): + """ + swap Security token for payment token + Arguments: + mm_num: market make index + st_val: security token amount to be swapped + st_isin: ISIN for security token + :return: + """ + swap_token_start(mm_num, st_val, st_isin) + transaction_number = 0 + exchange_contract, exchange_contract_address = get_exchange_contract() + + event_template = exchange_contract.events.approvalRequired + events = w3.eth.get_logs({}) + for event in events: + # does it get the last one??? this can give incorrect results + suc, res = handle_event(event=event, event_template=event_template) + if suc: + print("\nEvent found", res) + transaction_number = res["args"]["tx_number"] + if mm_addr[mm_num] == res["args"]["from"]: + pt_allowance_val = res["args"]["amount"] + increase_allowance_pt(mm_num, res["args"]["amount"]) + if treasury_addr == res["args"]["from"]: + st_allowance_val = res["args"]["amount"] + increase_allowance_st(st_isin, res["args"]["amount"]) + try: + # swap token + if delay: + print("\n introduced delay of 60 sec") + time.sleep(60) + swap_token_complete(transaction_number, st_isin, mm_num) + except Exception as e: + print("\nswap token failed - {}".format(e)) + print("\nDecreasing allowance") + decrease_allowance_pt(mm_num, pt_allowance_val) + decrease_allowance_st(st_isin, st_allowance_val) + + +def set_env_var(): + """ + export host value to environment + """ + cmd = 'source ./.env' + subprocess.call(cmd, shell=True, stdout=subprocess.PIPE) + + +def get_exchange_contract(): + """ + get exchange details + returns: + exchange contract object, exchange contract address + """ + with open('./resources/contracts.json','r') as file_read: + data = json.load(file_read) + exchange_contract_address = data["exchange_contract_address"] + exchange_bytecode, exchange_abi = compile_exchange() + exchange_contract = w3.eth.contract(address=exchange_contract_address, abi=exchange_abi) + return exchange_contract, exchange_contract_address + + +def get_balance(account, isin=None): + """ + get balance for an account + Arguments: + account: account address + isin: ISIN value of Security Token + """ + exchange_contract, exchange_contract_address = get_exchange_contract() + if isin: + balance_st = exchange_contract.caller({'from': exchange_addr}).get_balance_st(account, isin) + print("\nST Balance is : {}".format(balance_st)) + else: + json_file = open('./resources/security-tokens.json') + stks = json.load(json_file)["tokens"] + for stk in stks: + st_balance = exchange_contract.caller({'from': exchange_addr}).get_balance_st(account, + stk["isin"]) + print("\nST Balance for ISIN : {} is : {}".format(stk["isin"], st_balance)) + + balance_pt = exchange_contract.caller({'from': exchange_addr}).get_balance_pt(account) + print("\nPT Balance is : {}".format(balance_pt)) + diff --git a/bond_issuance_py/cli_parser.py b/bond_issuance_py/cli_parser.py new file mode 100644 index 000000000..9dfaa7c8e --- /dev/null +++ b/bond_issuance_py/cli_parser.py @@ -0,0 +1,64 @@ +import argparse +import os +from enum import Enum +from bond_poc import deploy_exchange, set_pt, mint_st, close_issuance, swap_tokens,set_env_var,setup_w3,mint_pt,get_balance + +class OperationType(str, Enum): + """ + class for defining operation type + """ + + DEPLOY_EXCHANGE= "deploy" + MINT_PT = "mint_pt" + MINT_ST = "mint_st" + SWAP = "swap" + CLOSE_ISSUANCE = "close_issuance" + GET_BALANCE = "get_balance" + +def main(): + parser = argparse.ArgumentParser(description='Run Bond POC') + # deploy exchange and PT + set_env_var() + host = os.environ['HOST'] + + parser.add_argument('--PTName', action='store', type=str, help='Name of payment token') + parser.add_argument('--PTSymbol', action='store',type=str, help='symbol of payment token') + parser.add_argument('--operation', action='store',type=str, help='value can be deploy, mint_st, mint_pt, swap, close_issuance or get_balance',required=True) + + # mint PT + parser.add_argument('--MMNumber', action='store',type=int, help='Market maker number(permissible value is 0,1,2)') + parser.add_argument('--PTAmountMint', action='store',type=int, help='PT amount to be minted for a Market maker') + + # swap + parser.add_argument('--STAmountSwap', action='store',type=int, help='ST amount to be to be swapped') + parser.add_argument('--ISIN', action='store',type=str, help='ISIN of a ST') + parser.add_argument('--Account', action='store',type=str, help='Account address') + parser.add_argument('--delay', action='store',type=bool, help='whether introduce delay in swap',default=False) + + args = parser.parse_args() + + setup_w3(host) + + + if args.operation.lower() == OperationType.DEPLOY_EXCHANGE: + deploy_exchange(args.PTName, args.PTSymbol) + set_pt() + elif args.operation.lower() == OperationType.MINT_ST: + mint_st() + elif args.operation.lower() == OperationType.MINT_PT: + mint_pt(args.MMNumber, args.PTAmountMint) + elif args.operation.lower() == OperationType.SWAP: + swap_tokens(args.MMNumber, args.STAmountSwap, args.ISIN, args.delay) + elif args.operation.lower() == OperationType.CLOSE_ISSUANCE: + close_issuance() + elif args.operation.lower() == OperationType.GET_BALANCE: + if args.ISIN: + get_balance(args.Account, args.ISIN) + else: + get_balance(args.Account) + else: + print("Invalid operation") + + +if __name__ == "__main__": + main() diff --git a/bond_issuance_py/contracts/Context.sol b/bond_issuance_py/contracts/Context.sol new file mode 100644 index 000000000..253659a96 --- /dev/null +++ b/bond_issuance_py/contracts/Context.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) + +pragma solidity ^0.8.0; + +/** + * @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; + } +} \ No newline at end of file diff --git a/bond_issuance_py/contracts/ERC20.sol b/bond_issuance_py/contracts/ERC20.sol new file mode 100644 index 000000000..56d558e97 --- /dev/null +++ b/bond_issuance_py/contracts/ERC20.sol @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/ERC20.sol) + +pragma solidity ^0.8.0; + +import "./IERC20.sol"; +import "./IERC20Metadata.sol"; +import "./Context.sol"; + +/** + * @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.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * 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 ERC20 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}. + * + * The default value of {decimals} is 18. To select a different value for + * {decimals} you should overload it. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @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 `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless this function is + * overridden; + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + 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 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; + } + _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; + _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; + } + _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 {} +} diff --git a/bond_issuance_py/contracts/ERC20Burnable.sol b/bond_issuance_py/contracts/ERC20Burnable.sol new file mode 100644 index 000000000..f564f3fb3 --- /dev/null +++ b/bond_issuance_py/contracts/ERC20Burnable.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/extensions/ERC20Burnable.sol) + +pragma solidity ^0.8.0; + +import "./ERC20.sol"; +import "./Context.sol"; + +/** + * @dev Extension of {ERC20} that allows token holders to destroy both their own + * tokens and those that they have an allowance for, in a way that can be + * recognized off-chain (via event analysis). + */ +abstract contract ERC20Burnable is Context, ERC20 { + /** + * @dev Destroys `amount` tokens from the caller. + * + * See {ERC20-_burn}. + */ + function burn(uint256 amount) public virtual { + _burn(_msgSender(), amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, deducting from the caller's + * allowance. + * + * See {ERC20-_burn} and {ERC20-allowance}. + * + * Requirements: + * + * - the caller must have allowance for ``accounts``'s tokens of at least + * `amount`. + */ + function burnFrom(address account, uint256 amount) public virtual { + _spendAllowance(account, _msgSender(), amount); + _burn(account, amount); + } +} diff --git a/bond_issuance_py/contracts/Exchange.sol b/bond_issuance_py/contracts/Exchange.sol new file mode 100644 index 000000000..82ad67040 --- /dev/null +++ b/bond_issuance_py/contracts/Exchange.sol @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: MIT +/** + * @file Exchange.sol + * @date created 1st Sept 2022 + * @date last modified 7th Sept 2022 + */ + +pragma solidity ^0.8.0; + +import "./PaymentToken.sol"; +import "./SecurityToken.sol"; +import "./Factory.sol"; + +contract Exchange is Factory{ + + PaymentToken public pt; + address private owner; + uint private tx_number; + + /** + * @dev map of tx number to tx details + */ + mapping(uint=>tx_details) private deadlines; + mapping(string=>address) private stAddress; + + /** + * @dev Structure of tx details + */ + struct tx_details { + SecurityToken st; + address market_maker; + address treasury; + uint bonds; + uint deadline; + bool isPresent; + } + + constructor (string memory _pt_name, string memory _pt_symbol) { + deployNewPT(_pt_name, _pt_symbol); + owner = msg.sender; + tx_number = 0; + } + + /** + * @dev Throws error if called by any account other than the owner. + */ + modifier onlyOwner() { + require(owner == msg.sender, "Ownable: caller is not the owner"); + _; + } + + event approvalRequired(uint tx_number, address from, address to, uint amount); + + /** + * @dev deploys Payment Token. + * called in constructor, private function. + */ + function deployNewPT (string memory _name, string memory _symbol) private returns (address){ + pt = new PaymentToken(_name, _symbol, 0); + return address(pt); + } + + /** + * @dev Mints new PT and transfer to the Market Maker + * Only be called by the owner + */ + function mintAndTransferPT(uint256 amount, address marketMaker) public onlyOwner{ + pt.mintAndTransfer(amount, marketMaker); + } + + /** + * @dev Mint new ST (bond series) and transfer it to Treasury + * Only be called by the owner + */ + function mintAndTransferST(string memory isin, address recipient, uint8 interest_rate, uint8 cost_in_pt, uint maturity_date, uint256 bond_amount) public onlyOwner{ + SecurityToken st = SecurityToken(deployNewST(address(this), isin, interest_rate, cost_in_pt, maturity_date,bond_amount)); + st.transfer(recipient, bond_amount); + stAddress[isin]= address(st); + } + + /** + * @dev Function to swap tokens, emits paynment approval events. Creates the tx details and add it to "deadlines" map + * returns the tx_number + */ + function swapTokens(address market_maker, address treasury, string memory isin, uint bonds) external onlyOwner returns (uint){ + require(st_map[isin].isPresent == true, "ISIN not present"); + SecurityToken st = SecurityToken(st_map[isin].st); + tx_number = tx_number + 1; + deadlines[tx_number].isPresent = true; + deadlines[tx_number].st = st; + deadlines[tx_number].market_maker = market_maker; + deadlines[tx_number].treasury = treasury; + deadlines[tx_number].bonds = bonds; + deadlines[tx_number].deadline = block.timestamp + 1 minutes; + emit approvalRequired(tx_number, market_maker, address(this), st.totalCost(bonds)); + emit approvalRequired(tx_number, treasury, address(this), bonds); + return tx_number; + } + + /** + * @dev Function to transfer tokens from MM to Treasury and vice versa, accepts the "tx_number" as input + * Atomic fucntion + * checks for dealine exceeded and tx_number validity + */ + function _swapTokens(uint _tx_number) public onlyOwner { + require(deadlines[_tx_number].isPresent == true, "Transaction not present"); + require(deadlines[_tx_number].deadline >= block.timestamp, "deadline exeeced to approve the tx"); + + address market_maker = deadlines[_tx_number].market_maker; + address treasury = deadlines[_tx_number].treasury; + uint bonds = deadlines[_tx_number].bonds; + SecurityToken st = deadlines[_tx_number].st; + + require( + pt.allowance(market_maker, address(this)) >= st.totalCost(bonds), + "PT allowance too low" + ); + require( + st.allowance(treasury, address(this)) >= bonds, + "ST allowance too low" + ); + + pt.transferFrom(market_maker, treasury, st.totalCost(bonds)); + st.transferFrom(treasury, market_maker, bonds); + } + + /** + * @dev Function to get PT balance for an account + */ + function get_balance_pt(address account) public view onlyOwner returns (uint256) { + return pt.balanceOf(account); + } + + /** + * @dev Function to get ST balance for an account and ISIN value + */ + function get_balance_st(address account, string memory isin) public view onlyOwner returns (uint256) { + require(st_map[isin].isPresent == true, "ISIN not present"); + return st_map[isin].st.balanceOf(account); + } + + /** + * @dev Function to get ST address based on ISIN value + */ + function getStAddress(string memory isin) public view onlyOwner returns (address) { + require(st_map[isin].isPresent == true, "ISIN not present"); + return stAddress[isin]; + } + + /** + * @dev Function to get PT address + */ + function getPtAddress() public view onlyOwner returns (address) { + return address(pt); + } +} \ No newline at end of file diff --git a/bond_issuance_py/contracts/Factory.sol b/bond_issuance_py/contracts/Factory.sol new file mode 100644 index 000000000..27b2f1c72 --- /dev/null +++ b/bond_issuance_py/contracts/Factory.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./SecurityToken.sol"; + +contract Factory { + + struct security_token { + SecurityToken st; + address stAddress; + bool isPresent; + } + /** + * @dev st_map stores the mapping of isin corresponding to respective security tokens(bond series) minted so far. + */ + mapping(string => security_token) public st_map; + event SecurityTokenCreated(address tokenAddress); + + + + /** + * @dev deploy a new ST with given ISIN + * returns the address of the new ST + */ + function deployNewST (address exchange, string memory isin, uint8 interest_rate, uint8 cost_in_pt, uint maturity_date, uint256 amount) internal returns (address){ + require(st_map[isin].isPresent != true, "ISIN already present"); + SecurityToken st = new SecurityToken("security token", "ST", amount, interest_rate, cost_in_pt, isin, maturity_date, exchange); + emit SecurityTokenCreated(address(st)); + st_map[isin].st = st; + st_map[isin].stAddress = address(st); + st_map[isin].isPresent = true; + return address(st); + } +} \ No newline at end of file diff --git a/bond_issuance_py/contracts/IERC20.sol b/bond_issuance_py/contracts/IERC20.sol new file mode 100644 index 000000000..2c6e90120 --- /dev/null +++ b/bond_issuance_py/contracts/IERC20.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.6.0) (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); +} \ No newline at end of file diff --git a/bond_issuance_py/contracts/IERC20Bond.sol b/bond_issuance_py/contracts/IERC20Bond.sol new file mode 100644 index 000000000..162cf9f5a --- /dev/null +++ b/bond_issuance_py/contracts/IERC20Bond.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) + +pragma solidity ^0.8.0; + +import "./IERC20.sol"; + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + * + * _Available since v4.1._ + */ +interface IERC20Bond is IERC20 { + /** + * @dev Returns the ISIN of the token. + */ + function ISIN() external view returns (string memory); + + /** + * @dev Returns the maturity_date of the token. + */ + function maturity_date() external view returns (uint); + + /** + * @dev Returns the interest_rate of the token. + */ + function interest_rate() external view returns (uint8); + + /** + * @dev Returns the cost_in_pt of the token. + */ + function cost_in_pt() external view returns (uint8); + + /** + * @dev Returns the owner of the token. + */ + function owner() external view returns (address); + + +} + diff --git a/bond_issuance_py/contracts/IERC20Metadata.sol b/bond_issuance_py/contracts/IERC20Metadata.sol new file mode 100644 index 000000000..fcedef50a --- /dev/null +++ b/bond_issuance_py/contracts/IERC20Metadata.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) + +pragma solidity ^0.8.0; + +import "./IERC20.sol"; + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + * + * _Available since v4.1._ + */ +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); +} \ No newline at end of file diff --git a/bond_issuance_py/contracts/PaymentToken.sol b/bond_issuance_py/contracts/PaymentToken.sol new file mode 100644 index 000000000..ab5551d8f --- /dev/null +++ b/bond_issuance_py/contracts/PaymentToken.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./ERC20.sol"; + +contract PaymentToken is ERC20 { + address private owner; + constructor( + string memory name, + string memory symbol, + uint256 initialSupply + ) ERC20(name, symbol) { + _mint(msg.sender, initialSupply); + owner = msg.sender; + } + + /** + * @dev Throws error if called by any account other than the owner. + */ + modifier onlyOwner() { + require(owner == msg.sender, "Ownable: caller is not the owner"); + _; + } + + function mintAndTransfer(uint256 amount, address recipient) external onlyOwner{ + _mint(msg.sender, amount); + transfer(recipient, amount); + } +} \ No newline at end of file diff --git a/bond_issuance_py/contracts/SecurityToken.sol b/bond_issuance_py/contracts/SecurityToken.sol new file mode 100644 index 000000000..0b078a89f --- /dev/null +++ b/bond_issuance_py/contracts/SecurityToken.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/ERC20.sol) + +pragma solidity ^0.8.0; + +import "./IERC20Bond.sol"; +import "./ERC20Burnable.sol"; + +/** + * @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.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * 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 SecurityToken is ERC20Burnable, IERC20Bond{ + + string private _ISIN; + uint private _maturity_date; + uint8 private _interest_rate; + uint8 private _cost_in_pt; + address private _owner; + + /** + * @dev Sets the values for {name} and {symbol}. + * + * The default value of {decimals} is 18. To select a different value for + * {decimals} you should overload it. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor( + string memory name, + string memory symbol, + uint256 initialSupply, + uint8 interest_rate_, + uint8 cost_in_pt_, + string memory ISIN_, + uint maturity_date_, + address owner_ + ) ERC20(name, symbol) { + _owner = owner_; + _ISIN = ISIN_; + _maturity_date = maturity_date_; + _interest_rate = interest_rate_; + _cost_in_pt = cost_in_pt_; + _mint(_owner, initialSupply); + } + + /** + * @dev Returns the ISIN, unique to each bond series + */ + function ISIN() public view virtual override returns (string memory) { + return _ISIN; + } + + /** + * @dev Returns maturity date, a string + */ + function maturity_date() public view virtual override returns (uint) { + return _maturity_date; + } + + /** + * @dev Returns interest rate for a bond (unique to bond series) + */ + function interest_rate() public view virtual override returns (uint8) { + return _interest_rate; + } + + /** + * @dev Returns cost in payment token + */ + function cost_in_pt() public view virtual override returns (uint8) { + return _cost_in_pt; + } + + /** + * @dev Returns owner + */ + function owner() public view virtual override returns (address) { + return _owner; + } + + /** + * @dev Returns total cost in PT for given number of bonds + */ + function totalCost (uint bonds) public view returns(uint){ + return cost_in_pt()*bonds; + } + +} + diff --git a/bond_issuance_py/readme.md b/bond_issuance_py/readme.md new file mode 100644 index 000000000..925f7c68d --- /dev/null +++ b/bond_issuance_py/readme.md @@ -0,0 +1,64 @@ +# Bond Issuance Python Dapp + +Commands to run this project. + +Install the requirements. + +```shell +pip3 install -r requirements.txt +``` + +Change in .env file and then run following command: + +```shell +cd vmbc-eth-sdk/tools/bond_issuance_py +``` + +Deploy exchange contract + +```shell +python cli_parser.py --PTName NIS --PTSymbol NIS --operation deploy +``` + +Mint PT for Market Makers + +```shell +python cli_parser.py --operation mint_pt --MMNumber 1 --PTAmountMint 1000 +``` +```shell +python cli_parser.py --operation mint_pt --MMNumber 0 --PTAmountMint 1000 +``` + +Mint ST + +```shell +python cli_parser.py --operation mint_st +``` + +Swap tokens + +```shell +python cli_parser.py --operation swap --MMNumber 0 --STAmountSwap 1 --ISIN ABCD0001 +``` + +Close issuance + +```shell +python cli_parser.py --operation close_issuance +``` + +Get balance for a specific account + +```shell +# for all ISIN and PT + python cli_parser.py --operation get_balance --Account + + # for specific ISIN and PT + python cli_parser.py --operation get_balance --Account --ISIN ABCD0002 + +``` + +Simulate deadline exceeded +```shell + python cli_parser.py --operation swap --MMNumber 0 --STAmountSwap 1 --ISIN ABCD0001 --delay True +``` diff --git a/bond_issuance_py/requirements.txt b/bond_issuance_py/requirements.txt new file mode 100644 index 000000000..6b4bb0baf --- /dev/null +++ b/bond_issuance_py/requirements.txt @@ -0,0 +1,3 @@ +py_solc_x==1.1.1 +urllib3==1.26.11 +web3==5.25.0 \ No newline at end of file diff --git a/bond_issuance_py/resources/accounts.json b/bond_issuance_py/resources/accounts.json new file mode 100644 index 000000000..ab06f4051 --- /dev/null +++ b/bond_issuance_py/resources/accounts.json @@ -0,0 +1,16 @@ +{ + "exchange": "exchange-address", + "exchange_key": "exchange-key", + "treasury": "treasury-address", + "treasury_key": "treasury-key", + "mm": [ + "mm-address-0", + "mm-address-1", + "mm-address-2" + ], + "mm_keys": [ + "mm-key-0", + "mm-key-1", + "mm-key-2" + ] +} diff --git a/bond_issuance_py/resources/contracts.json b/bond_issuance_py/resources/contracts.json new file mode 100644 index 000000000..81b7e819f --- /dev/null +++ b/bond_issuance_py/resources/contracts.json @@ -0,0 +1,4 @@ +{ + "exchange_contract_address": "", + "pt_address": "" +} diff --git a/bond_issuance_py/resources/security-tokens.json b/bond_issuance_py/resources/security-tokens.json new file mode 100644 index 000000000..06effcbf8 --- /dev/null +++ b/bond_issuance_py/resources/security-tokens.json @@ -0,0 +1,18 @@ +{ + "tokens": [ + { + "isin": "ABCD0001", + "interest_rate": 15, + "cost_in_pt": 10, + "maturity_date": 12345678, + "bond_amount": 20 + }, + { + "isin": "ABCD0002", + "interest_rate": 13, + "cost_in_pt": 20, + "maturity_date": 12345678, + "bond_amount": 40 + } + ] +} \ No newline at end of file