I'm building an application that is comprised of 3 smart contracts. The aim is to have the controller control the other two (contract A and B in the image). Previously, if I wanted to restrict the access to a smart contact, I would do it through a modifier
Example:
modifier onlyController {
require(msg.sender == controller);
_;
}
At contact creation, the smart contract would set controller to be equal to whatever ethereum address I want to make these calls ( for example the address that deployed the smart contract). The issue is that now I want the address of the controller smart contract to be the controller (in the modifier). How would it be best to do this considering I want to deploy these set of smart contacts at the same time using truffle. What would be the best way to link these, so that the controller smart contract is only able to make calls to A and B. Moreover, how does the controller have to be implemented so that the user can call function in A and B going through the controller ( so the user calls a function in the controller, then the controller calls the corresponding function in A or B)?
I am no expert in Solidity. But trying to answer as I find it little bit interesting.
You seem to be trying to implement a proxy pattern which is very common in other programming languages like java in such a way that you are providing very restricted access to certain classes in this case contracts.
In Proxy design pattern both the proxy and the class to be restricted would be implementing the same interface! I do not know much about your smart contract details.
Assuming Contract A and B implementing the same interface:
Lets assume the interface is Base.
interface Base{
function getValue() external view returns (string);
}
I need to provide controlled access to the ContractA and ContractB so only the controller could call. So lets create a modifier.
contract ControlledAccess{
address controller;
constructor() public {
controller = msg.sender;
}
modifier onlyController() {
require(msg.sender == controller);
_;
}
}
Now ContractA and ContractB should implement interface Base and inherit ControlledAccess contract.
contract ContractA is Base, ControlledAccess{
function getValue() public view onlyController returns (string){
return "Hi";
}
}
contract ContractB is Base, ControlledAccess{
function getValue() public view onlyController returns (string){
return "Hello";
}
}
In order to set the controller address to be the ProxyController address, ProxyController itself should create these contracts in its constructor. As our ProxyController contract should be able to control more than 1 contract, I think mapping might be a good choice.
contract ProxyController is Base{
string public contractKey = "a";
mapping(string => Base) base;
constructor() public {
base["a"]=new ContractA();
base["b"]=new ContractB();
}
function setContractKey(string _contractKey) public{
contractKey = _contractKey;
}
function getValue() public view returns (string){
return base[contractKey].getValue();
}
}
so you can switch to A & B via setContractKey.
Assuming there is no common functionalities between A and B :
Remove the interface in the above example.Then implement something like this.
contract ProxyController{
ContractA a;
ContractB b;
constructor() public {
a=new ContractA();
b=new ContractB();
}
function AgetValue() public view returns (string){
return a.getValue();
}
function BgetValue() public view returns (string){
return b.getValue();
}
}
I tested this and it seems to be working fine. However I am not sure of other issues like performance etc.
Can't you easily set up the controller contract as
contract ControllerContract is ContractA, ContractB {
...
}
Thus giving it access to the functions in both contract A and contract B?
I'm not sure why this way would be worse than how you are describing.
You can find some solutions at the following links:
1.) https://ethereum.stackexchange.com/questions/36524/multiple-smart-contracts
2.) https://ethereum.stackexchange.com/questions/37055/where-how-to-host-several-connected-smart-contracts
A complete article on Interactions between Smart Contracts with Solidity:
https://zupzup.org/smart-contract-interaction/
A link to another article and a sample project: https://hackernoon.com/ethereum-smart-contracts-lifecycle-multiple-contracts-message-sender-e9195ceff3ec
Hope it solves your problem. Also, you can join this community for exclusivity: https://ethereum.stackexchange.com/
Related
The following is some stripped down sample code which I am using to formulate my question. This pertains to the Youtube Video by Patrick Collins entitled: How to make NFT Art with On-Chain Metadata | FULL HARDHART / JS TUTORIAL! (w/ Polygon & Opensea)
contract RandomSVG is ERC721URIStorage, VRFConsumerBase {
....
....
function create() public returns (bytes32 requestId) {
requestId = requestRandomness(keyhash, fee);
...
...
}
function fulfillRandomness(bytes32 requestId, uint256 randomNumber)
internal
override
{
...
...
_safeMint(nftOwner, tokenId);
...
}
}
The RandomSVG is a contract that will create random NFTs.
Question 1 :
I read the documentation but i'm still unable to understand. What does the _safeMint() function exactly do ?
Question 2: At what point in the contract should the _safeMint() function be called ?
In his example, Patrick is using the _safeMint() function inside the fulfillRandomness() callback. Why is the _safeMint() called even before the Image has been created ? Shouldn't it be called After the TokenURI has been created ?
_safeMint() is used along with IERC721Receiver that checks if you are sending the minted token to a Contract that is capable to manage NFTs or not. This is to prevent tokens to be lost.
I am looking to explore the option of creating a digital certificate (as in proof) when someone has completed a portion of training, and for this to be issued on an EVM-compatible blockchain using Solidity.
I have prototyped using ERC721 NFTs to encode a "certificate" however, I'd like to prevent recipients from being able to transfer these certificates. To prevent transfer, I attempted to use the Pause.sol functionality from OpenZeppelin, however, this would result in the entire contract being paused, as opposed to a specific tokenId.
Does anyone have any recommendation on an approach? Am I overcomplicating it if I don't want recipients to be able to trade the certificates (i.e. for them to remain static)? Any pointers would be much appreciated!
The simplest and most raw solution is to just set a mapping value.
pragma solidity ^0.8;
contract TrainingResults {
enum Stage {
NONE,
STAGE_1,
STAGE_2,
COMPLETED
}
mapping (address => Stage) public participantStage;
function setParticipantStage(address _graduate, Stage _stage) external {
require(msg.sender == address(0x123), "Not authorized");
participantStage[_graduate] = _stage;
}
}
Or if you want them to be able to see some kind of NFT in their wallet (that supports NFTs), you can modify the ERC-721 contract to disallow transfers.
For example the OpenZeppelin implementation uses a function named _beforeTokenTransfer() (GitHub link) that can be overwritten to disallow transfers altogether.
pragma solidity ^0.8;
import "#openzeppelin/contracts/token/ERC721/ERC721.sol";
contract TrainingResults is ERC721 {
constructor() ERC721("TrainingResults", "TR") {}
function _beforeTokenTransfer(address from,address to, uint256 tokenId) override internal {
// Allow only for the admin
// as this function is called on token mint as well
require(msg.sender == address(0x123), "Cannot transfer tokens");
}
}
As it is now, anyone can call the setMyString function in the FirstContract. I'm trying to restrict access to that function to an instance of SecondContract. But not one specific instance, any contract of type SecondContract should be able to call setMyString.
contract FirstContract{
String public myString;
function setMyString(String memory what) public {
myString=what;
}
}
contract SecondContract{
address owner;
address firstAddress;
FirstContract firstContract;
constructor(address _1st){
owner=msg.sender;
firstAddress=_1st;
firstContract=FirstContract(firstAddress);
}
function callFirst(String memory what){
require(msg.sender==owner);
firstContract.setMyString("hello");
}
}
Solidity currently doesn't have an easy way to validate an address against an interface.
You can check the bytecode, whether it contains the specified signatures (of the public properties and methods). This requires a bit larger scope than a usual StackOverflow answer, so I'm just going to describe the steps instead of writing the code.
First, define the desired list of signatures (1st 4 bytes of keccak256 hash of the name and arguments datatypes) that you're going to be looking for. You can find more info about signatures in my other answers here and here.
An example in the documentation shows how to get any address's (in your case msg.sender) bytecode as bytes (dynamic-length array).
You'll then need to loop through the returned bytes array and search for the 4-byte signatures.
If you find them all, it means that msg.sender "implements the interface". If any of the signatures is missing in the external contract, it means it doesn't implement the interface.
But... I'd really recommend you to rethink your approach to whitelisting. Yes, you'll need to maintain the list and call setIsSecondContract() when a new SecondContract wants to call the setMyString() function for the first time. But it's more gas efficient for all callers of the FirstContract's setMyString() function, as well as easier to write and test the functionality in the first place.
contract FirstContract{
String public myString;
address owner;
mapping (address => bool) isSecondContract;
modifier onlySecondContract {
require(isSecondContract[msg.sender]);
_;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function setIsSecondContract(address _address, bool _value) public onlyOwner {
isSecondContract[_address] = _value;
}
function setMyString(String memory what) public onlySecondContract {
myString=what;
}
}
I have a problem about handle function value.
likes the title ..
I already know contract's function call other contract's function depend on:
addr.call(abi.encodeWithSignature("function(parameter_type)", parameter ));
but what if I want to handle the function's value(ex:bool) temporarily (memory) for condition .
I had seen abi.encodePacked() , but I don't even know what parameter feedback to me (compile error , different parameter type), that I can't even storage it .
Some articles write it only for bytes, uint , but I only want to do condition(bool).
You don't need to do that .call unless you want to preserve the msg.sender.
If the msg.sender does not matter for you, I recommend you to look at interfaces. Now, what you have to do, is to create an interface with the functionB definition of that you want to call, and then call it.
Example:
(some deployed contract)
contract MySecondContract {
function functionB() public returns(bool) {
return true;
}
}
(get the deployed contract address and use it in your code like)
interface IMySecondContract {
function functionB() external returns(bool);
}
contract MyFirstContract {
function functionA() public returns(bool) {
bool result = IMySecondContract(MySecondContract-address-here).functionB();
return result;
}
}
If you do want to keep using .call, here you can see that the .call method returns two values.
Something like
(bool success, bytes myReturn) = addr.call(...);
and then this, might work.
Something else that might help is this new way of debugging.
I am developing an Ethereum based Card Game. A user can collect n amount of individual/unique Cards. Everything is up and running, I am using the following balance mapping:
mapping (address => mapping (uint256 => uint256)) balances;
The first uint is the Card ID, the second uint is the Card count. I will have up to 1000 Cards, right now I am testing with 700 Cards.
I retrieve the balances on DApp Start by calling:
function balanceOf(address _owner, uint256 _id) view external returns(uint256) {
return balances[_owner][_id];
}
for every single ID. On balance changes I do partial balance updates. This generally works. It is free, but it is also extremely slow as the initial retrieval call has to be done 640 times. I have researched a lot and also tried various implementations, but the main problem is that I need to retrieve an address mapped array holding the Card ID and Count information. Currently you can not easily retrieve dynamic sized Arrays or Structs.
What would be the proposal to resolve the issue? Am I stuck with up to 1000 balanceOf calls on DApp Start until Solidity introduces simple Array calls?
I thought about caching data on my WebServer, but for this to work I would need to run a node on the WebServer which I would like to avoid.
A Client based caching, where the Client posts the balance to the WebServer may also run into an inconsistent state because of the asynchronous nature of the Blockchain.
You can also use struct for managing data more easily. I made one contract using struct and retained data too. Please let me know if this approach didnt work for you.
pragma solidity ^0.4.18;
contract Test{
struct User{
uint cardId;
uint cardCount;
}
address user_address;
mapping (address => User) public Users;
function add_user() public {
user_address = msg.sender ;
var new_user = Users[user_address];
new_user.cardId =2;
new_user.cardCount = 50;
}
function get() public returns(uint,uint)
{
return(Users[user_address].cardId,Users[user_address].cardCount);
}
}
Your best chance is to use a secondary mapping to store the card IDs some user has, query it first, and than, for each ID, check the count for that user and that card ID.
Here is the code, tested on Remix:
pragma solidity 0.4.24;
contract Test {
mapping (address => uint[]) cardsOwned;
mapping (address => mapping (uint => uint)) cardsCounter;
function cardsOwnedBy(address _owner) view public returns (uint[]) {
return (cardsOwned[_owner]);
}
function cardsCounterFor(address _owner, uint _id) view public returns (uint) {
return cardsCounter[_owner][_id];
}
}
I kept on trying various different solutions but couldn't find any good way to handle this. I found a setup which will work for me for now, until Solidity is updated to be more functional when it comes to array handling and especially dynamically sized variables.
Fastest solution for my requirements:
mapping (address => uint256[1000]) public balances;
The mapping now assigns addresses to a fixed size array. I can now retrieve the full list on DApp Start by using:
function balancesOf(address _owner) view external returns (uint256[1000]) {
return balances[_owner];
}
The main advantage is that it is extremely fast, compared to any other solution. The main disadvantage is, that I lost my dynamically sized array and I have to know the maximum Card Count in advance - which I do not. I used a safety buffer now but if I hit the 1000 Cards mark I will have to update the contract. Hopefully there will be better solutions in the future.