Last active
January 12, 2022 15:38
-
-
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=
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: 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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: 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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: 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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: 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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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) | |
} | |
})() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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