Smart Contracts - Chainlink VRF / #openzeppelin/truffle-upgrades compatibility - random

Right now i am transforming a basic smart contract into a upgradeable smart contract using #openzeppelin/truffle-upgrades
So i followed all required steps from the docs but one issue remains:
Truffle-Upgrades requires me to replace the constructor with an initializer which is fine for me, but not for the smart contracts imported into my own smart contract, sample:
pragma solidity 0.6.6;
import "#chainlink/contracts/src/v0.6/VRFConsumerBase.sol";
contract Sample is VRFConsumerBase {
address private owner;
bytes32 internal keyHash;
uint256 internal fee;
constructor(address _owner)
VRFConsumerBase(
0xa555fC018435bef5A13C6c6870a9d4C11DEC329C, // VRF Coordinator
0x84b9B910527Ad5C03A9Ca831909E21e236EA7b06 // LINK Token
) public
{
keyHash = 0xcaf3c3727e033261d383b315559476f48034c13b18f8cafed4d871abe5049186;
fee = 0.1 * 10 ** 18; // 0.1 LINK (Varies by network)
owner = _owner;
}
...
And therefore truffle complains:
../#chainlink/contracts/src/v0.6/VRFConsumerBase.sol:182: Contract `VRFConsumerBase` has a constructor
Define an initializer instead
As it is a third party package i can not replace it :)
Are there any architectural tricks/configurations?
I went through pretty much all docs on chainlink/truffle but did not find a solution for this issue.
Thanks!
UPDATE 1:
First of all, i modified the VRFConsumerBase contract to: (i also removed the comments to keep it short..)
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
import "#chainlink/contracts/src/v0.6/vendor/SafeMathChainlink.sol";
import "#chainlink/contracts/src/v0.6/interfaces/LinkTokenInterface.sol";
import "#chainlink/contracts/src/v0.6/VRFRequestIDBase.sol";
abstract contract VRFConsumerBaseUpgradable is VRFRequestIDBase {
using SafeMathChainlink for uint256;
function fulfillRandomness(bytes32 requestId, uint256 randomness)
internal virtual;
function requestRandomness(bytes32 _keyHash, uint256 _fee, uint256 _seed)
internal returns (bytes32 requestId)
{
LINK.transferAndCall(vrfCoordinator, _fee, abi.encode(_keyHash, _seed));
uint256 vRFSeed = makeVRFInputSeed(_keyHash, _seed, address(this), nonces[_keyHash]);
nonces[_keyHash] = nonces[_keyHash].add(1);
return makeRequestId(_keyHash, vRFSeed);
}
// removed immutable keyword <--
LinkTokenInterface internal LINK;
// removed immutable keyword <--
address private vrfCoordinator;
mapping(bytes32 /* keyHash */ => uint256 /* nonce */) private nonces;
// replaced constructor with initializer <--
function initialize(address _vrfCoordinator, address _link) public {
vrfCoordinator = _vrfCoordinator;
LINK = LinkTokenInterface(_link);
}
function rawFulfillRandomness(bytes32 requestId, uint256 randomness) external {
require(msg.sender == vrfCoordinator, "Only VRFCoordinator can fulfill");
fulfillRandomness(requestId, randomness);
}
}
What did i do:
I replaced the constructor with an initializer
I removed the immutable keyword from the state variables
Next, i used the Initializable contract from #openzeppelin/contracts-upgradeable in my file system to prevent the smart contract executing the initializer more than once:
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
abstract contract Initializable {
bool private _initialized;
bool private _initializing;
modifier initializer() {
require(_initializing || !_initialized, "Initializable: contract is already initialized");
bool isTopLevelCall = !_initializing;
if (isTopLevelCall) {
_initializing = true;
_initialized = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
}
}
}
Important:
I did not import the Initializable contract via the import statement in solidity.
Instead i copied the source code manually and set the compiler to 0.6.12 because #openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol is running on 0.8.x
Finally, i updated my contract to implement the Initializable and the new VRFConsumerBaseUpgradable contract:
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
import "./Initializable.sol";
import "./VRFConsumerBaseUpgradable.sol";
contract Sample is Initializable, VRFConsumerBaseUpgradable {
bytes32 internal keyHash;
uint256 internal fee;
address private owner;
function initialize(address _owner)
public
initializer
{
VRFConsumerBaseUpgradable.initialize(
0xa555fC018435bef5A13C6c6870a9d4C11DEC329C, // VRF Coordinator
0x84b9B910527Ad5C03A9Ca831909E21e236EA7b06 // LINK Token
);
keyHash = 0xcaf3c3727e033261d383b315559476f48034c13b18f8cafed4d871abe5049186;
fee = 0.1 * 10 ** 18;
owner = _owner;
}
function getRandomNumber(uint256 userProvidedSeed) public returns (bytes32 requestId) {
require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK - fill contract with faucet");
return requestRandomness(keyHash, fee, userProvidedSeed);
}
function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
// logic
}
...
}
I tested an initial migration as well as an upgrade via truffle and it both worked so i think this is fine and i am leaving this for future researchers..
What do you think? Should i create a merge request for VRFConsumerBaseUpgradable?

Related

Why not using events instead of state variables, for requests to oracles?

Why not just emit events instead of using state variables, for requests to oracles? For example:
contract OracleConsumer {
address oracle = 0x123...; // known account of oracle service
modifier onlyBy(address account) {
require(msg.sender == account); _;
}
event OracleRequest(..., abi encoding of callback function )
function updateExchangeRate() {
event OracleRequest("USD", ..., "oracleResponse(bytes)" )
}
function oracleResponse(bytes response) onlyBy(oracle) {
// use the data
}
}
Instead of ones like in here

Solidity and Etheruem: Handling events while using the factory pattern

I am in the beginning stages on learning Solidity and hardhat. I found a tutorial that seems pretty good and I was able to get the code to work from the example here: https://dev.to/dabit3/building-scalable-full-stack-apps-on-ethereum-with-polygon-2cfb. Let's say I wanted to expand this example and add factory pattern functionality to it. If a method is getting called through a factory method, how do I ensure events are propogated out past the factory method? In this case, how would I handle the event(s) in the sample.js file?
NFTMarketFactory.sol:
// contracts/Market.sol
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.3;
import "./NFTMarket.sol";
import "#openzeppelin/contracts/security/ReentrancyGuard.sol";
import "#openzeppelin/contracts/token/ERC721/ERC721.sol";
contract NFTMarketFactory is ReentrancyGuard {
address address;
address owner;
constructor() {
owner = payable(msg.sender);
}
.
.
.
function createMarketItem(
address nftContract,
uint256 tokenId,
uint256 price
) public payable nonReentrant {
NFTMarket market = NFTMarket(address);
return market.createMarketItem{value: msg.value}(address, tokenId, price, msg.sender);
}
}
NFTMarket.sol
/* Places an item for sale on the marketplace */
function createMarketItem(
address nftContract,
uint256 tokenId,
uint256 price,
address sender
) public payable nonReentrant {
require(price > 0, "Price must be at least 1 wei");
require(msg.value == listingPrice, "Price must be equal to listing price");
_itemIds.increment();
uint256 itemId = _itemIds.current();
idToMarketItem[itemId] = MarketItem(
itemId,
nftContract,
tokenId,
payable(sender),
payable(address(0)),
price,
false
);
IERC721(nftContract).transferFrom(sender, address(this), tokenId);
emit MarketItemCreated(
itemId,
nftContract,
tokenId,
sender,
address(0),
price,
false
);
}
test/sample.js
.
.
.
/* create two tokens */
let token1 = await nft.createToken("https://www.mytokenlocation.com");
let token2 = await nft.createToken("https://www.mytokenlocation2.com");
/* put both tokens for sale */
await nftMarketFactory.createMerchandise(nftContractAddress, 1, auctionPrice, { value: listingPrice })
await nftMarketFactory.createMerchandise(nftContractAddress, 2, auctionPrice, { value: listingPrice })
const [_, buyerAddress] = await ethers.getSigners()
/* execute sale of token to another user */
await nftMarketFactory.connect(buyerAddress).createMarketSale(nftContractAddress, 1, { value: auctionPrice})
.
.
.

Transferring assets between accounts and pallet

I'm trying to create a pallet that users can deposit assets into and withdraw from.
I've written the following code, but I'm not sure it's the best way to about things due to frame_system::RawOrigin::Root.into() being accessible by every runtime.
I'm still fairly new to Substrate and not sure this is exactly how it works, would love some guidance on the best design choice.
Making use of assets pallet to deposit:
<Assets::Module<T>>::transfer(origin, asset_id, RawOrigin::Root.into(), amount);
To Withdraw:
<Assets::Module<T>>::transfer(RawOrigin::Root.into(), asset_id, origin, amount);
Edit
A similar idea written in Solidity:
contract DepositWithdrawSend {
using SafeMath for uint256;
mapping (address => mapping (address => uint256)) public depositInfo;
address public sendPallet;
constructor(address _sendPallet) public {
sendPallet = _sendPallet;
}
function deposit(address _token, uint256 _amount) public {
IERC20(_token).transferFrom(msg.sender, address(this), amount);
depositInfo[_token][msg.sender] = depositInfo[_token][msg.sender].add(_amount);
}
function withdraw(address _token, uint256 _amount) public {
require(depositInfo[_token][msg.sender] >= _amount, "Over withdraw");
require(IERC20(_token).balanceOf(address(this)) >= _amount, "Not enough");
IERC20(_token).transfer(msg.sender, amount);
depositInfo[_token][msg.sender] = depositInfo[_token][msg.sender].sub(_amount);
}
function send(address _token, uint256 _amount) public {
require(IERC20(_token).balanceOf(address(this)) >= _amount, "Not enough");
IERC20(_token).transfer(sendPallet, amount);
}
}
We follow a pretty simple pattern to give pallets their own "account" for transferring balances to or anything else.
First you create a unique PalletId representing your pallet:
use frame_support::PalletId;
const MyPalletId: PalletId = PalletId(*b"replace_");
Then from here, you can generate an AccountId from this PalletId:
use sp_runtime::traits::AccountIdConversion;
/// These actually do computation. If you need to keep using them,
/// then make sure you cache the value and only call them once.
pub fn account_id() -> T::AccountId {
T::PalletId::get().into_account()
}
pub fn sub_account(seed: u16) -> T::AccountId {
// only use two byte prefix to support 16 byte account id (used by test)
// "modl" ++ "replace_" ++ "hi" is 14 bytes, and two bytes remaining for bounty index
T::PalletId::get().into_sub_account(("hi", id))
}
This pattern is used in the Treasury Pallet and others.

Hapi Fhir DomainResource, What URL do you use?

http://hapifhir.io/doc_custom_structures.html
this article discusses a DomainResource.
There are situations however when you might want to create an entirely
custom resource type. This feature should be used only if there is no
other option, since it means you are creating a resource type that
will not be interoperable with other FHIR implementations.
I've implemented the code verbatum. (I show the classes below (with no "guts" just for brevity) (full code at the url))
/**
* This is an example of a custom resource that also uses a custom
* datatype.
*
* Note that we are extensing DomainResource for an STU3
* resource. For DSTU2 it would be BaseResource.
*/
#ResourceDef(name = "CustomResource", profile = "http://hl7.org/fhir/profiles/custom-resource")
public class CustomResource extends DomainResource {
}
and
/**
* This is an example of a custom datatype.
*
* This is an STU3 example so it extends Type and implements ICompositeType. For
* DSTU2 it would extend BaseIdentifiableElement and implement ICompositeDatatype.
*/
#DatatypeDef(name="CustomDatatype")
public class CustomDatatype extends Type implements ICompositeType {
}
And I've "registered it" in my code base:
if (null != this.fhirContext)
{
this.fhirContext.registerCustomType(CustomResource.class);
this.fhirContext.registerCustomType(CustomDatatype.class);
}
(~trying to follow the instructions from the URL above)
// Create a context. Note that we declare the custom types we'll be using
// on the context before actually using them
FhirContext ctx = FhirContext.forDstu3();
ctx.registerCustomType(CustomResource.class);
ctx.registerCustomType(CustomDatatype.class);
// Now let's create an instance of our custom resource type
// and populate it with some data
CustomResource res = new CustomResource();
// Add some values, including our custom datatype
DateType value0 = new DateType("2015-01-01");
res.getTelevision().add(value0);
CustomDatatype value1 = new CustomDatatype();
value1.setDate(new DateTimeType(new Date()));
value1.setKittens(new StringType("FOO"));
res.getTelevision().add(value1);
res.setDogs(new StringType("Some Dogs"));
// Now let's serialize our instance
String output = ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(res);
System.out.println(output);
But that looks like a console-app usage of two objects...not how to wire it into the fhir-server.
I've been trying for 3 hours now to figure out what URL to use.
some things I've tried:
http://127.0.0.1:8080/fhir/CustomResource
http://127.0.0.1:8080/fhir/profiles/custom-resource
http://127.0.0.1:8080/fhir/custom-resource
to no avail...
What is the URL?
And how do I populate the values for it?
Ok.
So the CustomResource still needs its own IResourceProvider. (thanks to daniels in the comments of the original question)
Here is a basic working example.
You'll do everything I listed in the original question AND you'll make and register an IResourceProvider for the new customresource.
new IResourceProvider
package mystuff.resourceproviders;
import org.hl7.fhir.dstu3.model.DateType;
import org.hl7.fhir.dstu3.model.IdType;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.server.IResourceProvider;
import mystuff.CustomDatatype;
import mystuff.CustomResource;
import org.hl7.fhir.dstu3.model.DateTimeType;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.util.Date;
public class CustomResourceProvider implements IResourceProvider {
#Override
public Class<? extends IBaseResource> getResourceType() {
return CustomResource.class;
}
/* the IdType (datatype) will be different based on STU2 or STU3. STU3 version below */
#Read()
public CustomResource getResourceById(#IdParam IdType theId) {
// Now let's create an instance of our custom resource type
// and populate it with some data
CustomResource res = new CustomResource();
res.setId(theId);
// Add some values, including our custom datatype
DateType value0 = new DateType("2015-01-01");
res.getTelevision().add(value0);
CustomDatatype value1 = new CustomDatatype();
value1.setDate(new DateTimeType(new Date()));
value1.setKittens(new StringType("FOO"));
res.getTelevision().add(value1);
res.setDogs(new StringType("Some Dogs"));
return res;
}
}
then you'll register this (as documented here):
http://hapifhir.io/doc_rest_server.html#_toc_create_a_server
instead of this:
#Override
protected void initialize() throws ServletException {
/*
* The servlet defines any number of resource providers, and
* configures itself to use them by calling
* setResourceProviders()
*/
List<IResourceProvider> resourceProviders = new ArrayList<IResourceProvider>();
resourceProviders.add(new RestfulPatientResourceProvider());
resourceProviders.add(new RestfulObservationResourceProvider());
setResourceProviders(resourceProviders);
}
you'll have something like this
#Override
protected void initialize() throws ServletException {
/*
* The servlet defines any number of resource providers, and
* configures itself to use them by calling
* setResourceProviders()
*/
List<IResourceProvider> resourceProviders = new ArrayList<IResourceProvider>();
resourceProviders.add(new CustomResourceProvider());
setResourceProviders(resourceProviders);
}
URL for testing this (most probable local development url that is)
http://127.0.0.1:8080/fhir/CustomResource/12345
and you'll get back this JSON response.
{
"resourceType": "CustomResource",
"id": "12345",
"meta": {
"profile": [
"http://hl7.org/fhir/profiles/custom-resource"
]
},
"televisionDate": [
"2015-01-01"
],
"televisionCustomDatatype": [
{
"date": "2019-01-14T11:49:44-05:00",
"kittens": "FOO"
}
],
"dogs": "Some Dogs"
}

How can I return an array of struct in solidity?

I am designing a solution for an ethereum smart contract that does bidding. The use-case includes reserving a name eg. "myName" and assigning to an address. And then, people can bid for that name (in this case myName). There can be multiple such biddings happening for multiple names.
struct Bid {
address bidOwner;
uint bidAmount;
bytes32 nameEntity;
}
mapping(bytes32 => Bid[]) highestBidder;
So, as you can see above, Bid struct holds data for one bidder, similarly, the key (eg. myName) in the mapping highestBidder points to an array of such bidders.
Now, I am facing a problem when I try to return something like highestBidder[myName].
Apparently, solidity does not support returning an array of structs (dynamic data). I either need to rearchitect my solution or find some workaround to make it work.
If you guys have any concerns regarding the question, please let me know, I will try to make it clear.
I am stuck here any help would be appreciated.
As you mentioned, this is not yet supported in Solidity. The powers that be are planning on changing it so you can, but for now, you have to retrieve the number of elements and then retrieve the decomposed struct as a tuple.
function getBidCount(bytes32 name) public constant returns (uint) {
return highestBidder[name].length;
}
function getBid(bytes32 name, uint index) public constant returns (address, uint, bytes32) {
Bid storage bid = highestBidder[name][index];
return (bid.bidOwner, bid.bidAmount, bid.nameEntity);
}
Edit to address question in comment regarding storage vs memory in this case
Local storage variables are pointers to state variables (which are always in storage). From the Solidity docs:
The type of the local variable x is uint[] storage, but since storage is not dynamically allocated, it has to be assigned from a state variable before it can be used. So no space in storage will be allocated for x, but instead it functions only as an alias for a pre-existing variable in storage.
This is referring to an example where the varable used is uint[] x. Same applies to my code with Bid bid. In other words, no new storage is being created.
In terms of cost:
getBid("foo", 0) using Bid memory bid:
getBid("foo", 0) using Bid storage bid:
In this case, storage is cheaper.
Return an array of struct in solidity?
In below function getBid returns array of bid structure.
contract BidHistory {
struct Bid {
address bidOwner;
uint bidAmount;
bytes32 nameEntity;
}
mapping (uint => Bid) public bids;
uint public bidCount;
constructor() public {
bidCount = 0;
storeBid("address0",0,0);
storeBid("address1",1,1);
}
function storeBid(address memory _bidOwner, uint memory _bidAmount, bytes32 memory _nameEntity) public {
bids[tripcount] = Bid(_bidOwner, _bidAmount,_nameEntity);
bidCount++;
}
//return Array of structure
function getBid() public view returns (Bid[] memory){
Bid[] memory lBids = new Bid[](tripcount);
for (uint i = 0; i < bidCount; i++) {
Bid storage lBid = bids[i];
lBids[i] = lBid;
}
return lBids;
}
}
About "returning an array of structs"... just a small workaround in order to return an array of structs extracted from medium
pragma solidity ^0.4.13;
contract Project
{
struct Person {
address addr;
uint funds;
}
Person[] people;
function getPeople(uint[] indexes)
public
returns (address[], uint[]) {
address[] memory addrs = new address[](indexes.length);
uint[] memory funds = new uint[](indexes.length);
for (uint i = 0; i < indexes.length; i++) {
Person storage person = people[indexes[i]];
addrs[i] = person.addr;
funds[i] = person.funds;
}
return (addrs, funds);
}
}
The uint[] index parameters should contain the indexes that you want to access.
Best

Resources