Skip to content

Instantly share code, notes, and snippets.

@vectorman1
Last active January 12, 2022 15:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vectorman1/16d8328f787d09002b7d82fed3eb742d to your computer and use it in GitHub Desktop.
Save vectorman1/16d8328f787d09002b7d82fed3eb742d to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=builtin&optimize=false&runs=200&gist=
REMIX EXAMPLE PROJECT
Remix example project is present when Remix loads very first time or there are no files existing in the File Explorer.
It contains 3 directories:
1. 'contracts': Holds three contracts with different complexity level, denoted with number prefix in file name.
2. 'scripts': Holds two scripts to deploy a contract. It is explained below.
3. 'tests': Contains one test file for 'Ballot' contract with unit tests in Solidity.
SCRIPTS
The 'scripts' folder contains example async/await scripts for deploying the 'Storage' contract.
For the deployment of any other contract, 'contractName' and 'constructorArgs' should be updated (along with other code if required).
Scripts have full access to the web3.js and ethers.js libraries.
To run a script, right click on file name in the file explorer and click 'Run'. Remember, Solidity file must already be compiled.
Output from script will appear in remix terminal.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
contract Ownable {
string public id;
uint immutable public price;
address public owner;
string public name;
uint immutable public blockBought;
constructor(string memory _id, address _owner, string memory _name, uint _price) {
id = _id;
owner = _owner;
name = _name;
price = _price;
blockBought = block.number;
}
error NotOwner(address sender, address txOrigin);
modifier onlyOwner() {
if (msg.sender != owner && tx.origin != owner) {
revert NotOwner(msg.sender, tx.origin);
}
_;
}
function transfer(address _target) onlyOwner external {
owner = _target;
}
}
// SPDX-License-Identifier: GPL-3.0
import {Ownable} from "./Ownable.sol";
pragma solidity ^0.8.0;
contract OwnableFactory {
// the address that created the factory
address immutable owner;
constructor(address _owner) {
owner = _owner;
}
function create(
string memory _id,
address _owner,
string memory _name,
uint _price)
external
returns (Ownable) {
Ownable ownable = new Ownable(_id, _owner, _name, _price);
return ownable;
}
}
// SPDX-License-Identifier: GPL-3.0
import {Ownable} from "./Ownable.sol";
import {OwnableFactory} from "./OwnableFactory.sol";
import { utils } from "./utils.sol";
pragma solidity ^0.8.0;
contract TechnolimeStore {
using utils for *;
address public owner;
OwnableFactory immutable ownableFactory;
// Stock items are uniquely identified by their ID and mapped for quick access
struct StockItem {
string id;
string name;
uint quantity;
uint price;
}
StockItem[] stock;
mapping(string => bool) stockInserted;
mapping(string => uint) stockIdx;
// Keep refunded items separately and transfer those instead of creating new contracts when available
// The mapping is StockItem.id -> Array of refunded contracts
mapping(string => Ownable[]) refunded;
// keep records for clients and mappings for quick access
address[] clients;
mapping(address => uint) clientIdx;
mapping(address => bool) clientInserted;
// Client -> Bought ownable mapping
mapping(address => Ownable) clientOwnable;
constructor() {
owner = msg.sender;
ownableFactory = new OwnableFactory(owner);
}
fallback() payable external {
}
receive() payable external {
}
error NotOwner();
error NotClient();
error NotEnoughEther();
modifier onlyOwner() {
if (msg.sender != owner) {
revert NotOwner();
}
_;
}
modifier onlyClient() {
if (msg.sender == owner) {
revert NotClient();
}
_;
}
function balance() onlyOwner public view returns (uint) {
return address(this).balance;
}
function withdraw(address _to, uint amount) onlyOwner public payable {
require(amount < balance(), "withdrawing more than you have");
(bool success, ) = payable(_to).call{value: amount}("");
if (!success) {
revert NotEnoughEther();
}
}
function addStock(string memory _id, string memory _name, uint _quantity, uint _price) onlyOwner public {
// If the stock was already inserted,
// we can just increment the requested quantity with the existing one.
uint stockIdIdx = stockIdx[_id];
bool inserted = stockInserted[_id];
if (inserted) {
StockItem storage existing = stock[stockIdIdx];
require(existing.name.stringEquals( _name), "existing stock; mismatched names");
require(existing.price == _price, "existing stock; mismatched price");
existing.quantity += _quantity;
return;
}
// Append the newly added item to the stock array
StockItem memory item = StockItem(_id, _name, _quantity, _price);
stock.push(item);
// Update relevant mappings we use in other functions
// The index of this product ID is the past, as we appended it
stockInserted[item.id] = true;
stockIdx[item.id] = stock.length - 1;
}
function buyItem(string memory _id) onlyClient payable public returns (Ownable) {
require(stockInserted[_id], "item not found");
require(clientOwnable[msg.sender] == Ownable(address(0)), "you've already bought from us");
// Get the referred stock item
uint itemIdx = stockIdx[_id];
StockItem storage item = stock[itemIdx];
require(item.quantity > 0, "not in stock");
require(item.price <= msg.value, "not enough ETH");
// If there is an existing refunded contract for this product ID
// transfer it to the buyer
// and delete it locally
Ownable ownable;
Ownable[] storage refundedOwnables = refunded[_id];
if (refundedOwnables.length > 0) {
ownable = refundedOwnables[refundedOwnables.length - 1];
ownable.transfer(msg.sender);
refundedOwnables.pop();
} else {
// if not we create a new contract
ownable = ownableFactory.create(item.id, msg.sender, item.name, item.price);
}
// Update the clients storage and mappings
saveClient(msg.sender, ownable);
// Update the available quantity
uint idx = stockIdx[item.id];
stock[idx].quantity -= 1;
return ownable;
}
// refund transfers the ownable to this, stores the adress of the ownable
function refund(Ownable _item) onlyClient payable public {
require(clientOwnable[msg.sender] == _item, "you did not buy this item");
require(block.number - _item.blockBought() < 100, "item was bought more than 100 blocks ago");
uint itemPrice = _item.price();
require(balance() >= itemPrice, "we can't pay for that, try again later");
_item.transfer(address(this));
// We get the Ownable's ID, and append it to the refunded mapping
// and update the stock quantity
string memory itemId = _item.id();
refunded[itemId].push(_item);
uint idx = stockIdx[itemId];
stock[idx].quantity += 1;
forgetClient(msg.sender);
(bool success,) = payable(msg.sender).call{value: itemPrice}("");
if (!success) {
revert NotEnoughEther();
}
}
struct SoldItem {
address client;
Ownable item;
}
function getClientSoldItems() public view returns (SoldItem[] memory) {
SoldItem[] memory soldItems = new SoldItem[](clients.length);
for (uint i = 0; i < clients.length; i++) {
address clientAddr = clients[i];
soldItems[i] = SoldItem(clientAddr, clientOwnable[clientAddr]);
}
return soldItems;
}
function getFullStock() public view returns (StockItem[] memory) {
return stock;
}
function forgetClient(address client) private {
uint idx = clientIdx[client];
clients.removeAddr(idx);
delete clientIdx[client];
delete clientOwnable[client];
delete clientInserted[client];
}
function saveClient(address client, Ownable ownable) private {
clients.push(client);
clientOwnable[client] = ownable;
clientIdx[client] = clients.length - 1;
clientInserted[client] = true;
}
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
library utils {
function stringEquals(string memory s1, string memory s2) internal pure returns (bool) {
bytes memory b1 = bytes(s1);
bytes memory b2 = bytes(s2);
uint256 l1 = b1.length;
if (l1 != b2.length) return false;
for (uint256 i=0; i<l1; i++) {
if (b1[i] != b2[i]) return false;
}
return true;
}
function removeAddr(address[] storage arr, uint _index) internal {
require(_index < arr.length, "index out of bound");
for (uint i = _index; i < arr.length - 1; i++) {
arr[i] = arr[i + 1];
}
arr.pop();
}
}
// Right click on the script name and hit "Run" to execute
(async () => {
try {
console.log('Running deployWithEthers script...')
const contractName = 'Storage' // Change this for other contract
const constructorArgs = [] // Put constructor args (if any) here for your contract
// Note that the script needs the ABI which is generated from the compilation artifact.
// Make sure contract is compiled and artifacts are generated
const artifactsPath = `browser/contracts/artifacts/${contractName}.json` // Change this for different path
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath))
// 'web3Provider' is a remix global variable object
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
let factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer);
let contract = await factory.deploy(...constructorArgs);
console.log('Contract Address: ', contract.address);
// The contract is NOT deployed yet; we must wait until it is mined
await contract.deployed()
console.log('Deployment successful.')
} catch (e) {
console.log(e.message)
}
})()
// Right click on the script name and hit "Run" to execute
(async () => {
try {
console.log('Running deployWithWeb3 script...')
const contractName = 'Storage' // Change this for other contract
const constructorArgs = [] // Put constructor args (if any) here for your contract
// Note that the script needs the ABI which is generated from the compilation artifact.
// Make sure contract is compiled and artifacts are generated
const artifactsPath = `browser/contracts/artifacts/${contractName}.json` // Change this for different path
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath))
const accounts = await web3.eth.getAccounts()
let contract = new web3.eth.Contract(metadata.abi)
contract = contract.deploy({
data: metadata.data.bytecode.object,
arguments: constructorArgs
})
const newContractInstance = await contract.send({
from: accounts[0],
gas: 1500000,
gasPrice: '30000000000'
})
console.log('Contract deployed at address: ', newContractInstance.options.address)
} catch (e) {
console.log(e.message)
}
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment