How can I write a smart contract to give role based permission for a transaction to take place.
Say there are five people A, B, C, D and E. A wants to send some ethers to B. But the transaction will not take place until and unless C, D and E give confirmation/approve.
Is it possible to do with ethereum smart contracts? Can someone give me sample code for the same?
Thanks in advance.
you can create such smart contract although using a multisig account would be better for this case.
you could write a simple contract which validate a transaction after receiving the different needed signatures.
e..g :
contract C{
address A;
address B;
address C;
mapping (address=>bool) permission;
function send_permission(address _to, uint value)
{
if(permission[A]&&permission[A]&&permission[A])
_to.transfer(value);
}
function set_permission(bool state)
{
permission[msg.sender]=state;
}
}
For a dynamic solution you will need to create 2 mappings one for the user who can approve transactions (lets call them moderator for simplicity) and one for the permissions
the logic will be when a user sends a transaction we see if he is approved in the approval mapping if yes go though with the transaction otherwise revert.
you will need to make a function for a user to get approved.
and a function which allows a moderator to approve an address in which we will check if a moderator is present in the moderators mapping. if yes he can add the non-approved user to the approved mapping.
remember in this solution once an address is approved that address can send multiple transactions with no checks but if you want to limit what address can send which transaction you'll have to modify the second mapping to a nested one in which youll keep a track of the address, the transaction number and a bool value
Related
According to the docs for web3.eth.sendTransaction and the docs for eth_sendTransaction:
The transaction object can contain an optional data parameter which should be a String that consists of:
either an ABI byte string containing the data of the function call on a contract, or in the case of a contract-creation transaction the initialisation code.
I want to assign a string to data and have the string be stored along with the record of the transaction on the blockchain, so that I can retrieve that string when I retrieve the record of that transaction later.
const [testAccount] = await window.ethereum.request({ method: "eth_requestAccounts" })
const web3 = new Web3(window.ethereum)
if (!testAccount) {
return
}
let transactionHash = await web3.eth.sendTransaction({
from: testAccount,
to: testAccount,
value: web3.utils.toWei('0.0003'),
data: web3.utils.utf8ToHex(JSON.stringify({ a: 1, b: 2 }))
})
let transaction = await web3.eth.getTransaction(transactionHash)
let data = JSON.parse(web3.utils.hexToUtf8(transaction.data))
console.log(data.a) // should log 1
When I execute sendTransaction (using Metamask, while connected to the Ropsten network), I get the following error:
Error: Error: TxGasUtil - Trying to call a function on a non-contract address
{
"originalError": {
"errorKey": "transactionErrorNoContract",
"getCodeResponse": "0x"
}
}
Apparently, you cannot assign any string to data and expect the string to be incorporated in the record of the transaction on the blockchain, out-of-the-box, simply by assigning a value to it. Is this correct?
Question: Do I need to write a custom smart-contract in order to achieve this ?
This is a feature/limitation specific to MetaMask. Possibly to protect their users who want to interact with a smart contract but are connected to a different network where the contract is not deployed.
However, it is technically possible to send a valid transaction with non-empty data field to a non-contract address. You just need to use a different node provider to broadcast the transaction. Unfortunately the node provider in MetaMask is hardcoded so it's not possible using this wallet.
Example: This transaction on the Ropsten testnet to the 0xdac1... address that has the USDT token contract deployed on the mainnet, but is a non-contract address on the testnet. It is a valid transaction, successfully bought gas from the sender address to cover the transaction fees, mined in a block, just didn't execute any smart contract code (as there is no smart contract on the recipient address).
Do I need to write a custom smart-contract in order to achieve this ?
You can also write a smart contract function in Solidity that receives data as a function argument, but does nothing on this. Thus, GoEthereum node stores this data as calldata of the transaction and it can be later retrieved.
I am pretty sure some cross-chain bridges operate in this manner. Transactions only write data as the part of calldata (cheaper than Solidity storage) and then other clients read it from there.
I am making a decentralized crowd funding smart contract in solidity to learn more about the language.
The contract is able to store many crowdfunding projects in a mapping that looks like this
struct CrowdFundingProject{
address author,
string description,
string title,
uint goal,
bool exists
}
mapping(uint, CrowdFundingProject) public projects;
uint lastProject = 0;
the key of the mapping is an autoincremented number. When you create a project it looks like this:
function createProject(string title, string desc, uint goal) public{
CrowdFundingProject newProject;
newProject.author = msg.sender;
newProject.title = title;
newProject.description = desc;
newProject.goal = goal;
newProject.exists = true;
lastProject += 1;
projects[lastProject] = newProject;
}
But i belive that this could cause problems, for example, if user A ran the function createProject and just when the EVM is executing lastProject += 1; User B runs the function too, making that if lastProject was equal to 100 it is now 101 at the same time that projects[lastProject] = newProject; wasn't done executing for user A, both would have a project with the ID 101. which would overwrite the project for user A and just leave the one for user B.
Is this correct? If so, my other approach is instead of directly adding the project as project[lastProject] it would be better to do something like
lastProject += 1;
uint myProjectsId = lastProject - 1;
projects[myProjectsId] = newProject;
I don't know if this approach would cause any sort of error, or if the overwriting error is even possible, so i want to make sure:
Can an error like the overlapping IDs happen?
If so: Would the second approach that I proposed come with any weird behavior?
if user A ran the function createProject and just when the EVM is executing lastProject += 1; User B runs the function too
The EVM executes transactions in series, not in parallel.
Transactions in each block are ordered in a way that the block miner chooses. Most miners simply order from largest gasPrice to smallest.
So based on the provided code, there's no way to overwrite the projects key, even if two separate transactions execute the createProject() function in the same block: The transaction with larger gasPrice will get project ID 100, and the other will execute milliseconds later and will get project ID 101.
There's an attack vector called frontrunning, related to ordering transactions in the block based on their gasPrice. It might be useful to read up about it, for example there's a great article describing the basics.
Your code is vulnerable to this vector, but it practically doesn't matter because the attacker could only frontrun someone and steal their desired project ID (leaving the original project creator with ID higher by 1). Usual incentives for frontrunners are stealing ETH and tokens (and your code doesn't show working with ETH nor tokens).
I'm trying to create a "real" transaction from inside a smart contract to an EOA. This is so that I can attach data/input_data to send to it.
I've read several resources on this but I've come to contradictory information: some say it's impossible, some say that call() can achieve this. I've been testing multiple methods and have not come to see that it is possible.
Here's a simple example of what I'm trying to achieve:
pragma solidity 0.8.6;
contract Simple {
uint32 value;
constructor() payable {
// Contract is initialized with 0.5 ether
value = 22;
}
function foo() public {
require(address(this).balance >= 0.1 ether);
// Along with transfering ether, I want to send some data to the EOA,
// for example, whichever value is in the variable "value"
payable(msg.sender).transfer(0.1 ether);
}
}
On a "normal" transaction, it is possible to set the field "input data" (normally used to make function calls when sending a transaction to a smart contract), which allows us to send data on a transaction from an EOA to another EOA. I was able to achieve this already.
But, from my understanding, contracts "can't" create transactions; they only create "internal transactions" (informal name) that are associated with the "parent transaction" (transaction that called the contract in the first place) and therefore don't have the data field. But they're able to call another contract on the network, so I assume they're able to send some data along the network, right?
Furthermore, this question seems to imply that the low level call() method is able to achieve this. I've tried multiple approaches but have not been able to reproduce the wanted behaviour.
msg.sender.call{value: 0.1 ether}("data", value); // Doesn't work
msg.sender.call{value: 0.1 ether}(value); // Doesn't work
msg.sender.call{value: 0.1 ether}(abi.encodeWithSignature(value)) // Doesn't work
At some point, I did find my message on a block's "extra data", but from my understanding, this was written there by the miner for some reason.
So, to sum it up, is it possible to, from a contract, send ether + message to an EOA account? How would I achieve this?
Edit: Fixed the function name, which was a reserved keyword.
function msg() public {
This function name is a bit problematic, because you're overriding the global msg variable, and can't use the msg.sender.
So I changed the function name to generic foo().
function foo() public {
msg.sender.call{value: 0.1 ether}
Since Solidity 0.8, an address is not payable by default (source: docs). So if you want to send them the native currency (in case of Ethereum, that's ETH), you need to cast the address to payable first.
payable(msg.sender).call{value: 0.1 ether}
Finally to the data sending part.
At some point, I did find my message on a block's "extra data", but from my understanding, this was written there by the miner for some reason.
I'm not exactly sure, but it seems like you stumbled upon the correct field, which is simply the data field of the raw transaction, and the blockchain explorer probably named it "extra data". The data field is not filled by the miner, but by the transaction creator.
Since you're sending an integer, and the data field is bytes (array of bytes), you need to encode it to bytes. In your case:
abi.encode(value)
To you put it all together:
pragma solidity 0.8.6;
contract Simple {
uint value;
constructor() payable {
value = 22;
}
function foo() public {
require(address(this).balance >= 0.1 ether);
payable(msg.sender).call{value: 0.1 ether}(abi.encode(value));
}
}
When you execute the foo() function (and the contract has enough funds), it will create an internal transaction (some blockchain explorers might use a different name) to the msg.sender with value 0.1 ether and the decimal 22 encoded as hex 16 in the data field.
If i have a function like this:
function sendToAuthor (uint tokenId) public payable{
//here I want to get some ether from the msg.sender, store it in a
mapping and send it to another address (author).
}
What I don't understand is how to check if the msg.sender gave the smart contract the money or not. If I can check that, I can take the msg.value from the mapping and send it to the author, but how do I check that the sender actually made the eth transfer to the contract?
The msg.value variable contains how much ETH was deposited in payable function. You check that the value is above zero or matches the any payment amount you are expecting.
psuedocode:
contract someContract {
address author = "0x00...";
mapping (address => uint) public sentToAuthor;
function sendToAuthor () public payable {
sentToAuthor[msg.sender] = msg.value;
author.call{value: msg.value}("");
}
}
Your question doesn't really make sense.
What I don't understand is how to check if the msg.sender gave the smart contract the money or not.
You just look at the value of msg.value. If the transaction doesn't revert, and the function is payable, and you don't send it anywhere else, then your contract receives those funds. Period. No "checking" needed. If you do want to check, you could on the client side, but just looking at the contract balance (e.g., ethers.utils.balanceOf(contractAddress), or something). You could also just look at the mapping here, it will be correct and show you.
If I can check that, I can take the msg.value from the mapping and send it to the author, but how do I check that the sender actually made the eth transfer to the contract?
The "msg.value" won't actually be "in" the mapping, mappings can't actually hold eth, they can only hold a uint. It just tells you how much was sent to the contract.
Btw, as written here, it sends it straight to the author, so it won't stay in the contract. If you remove the last line of "sendToAuthor" (the author.call line), then the eth will just stay in the contract itself, instead.
(btw, there's an ethereum stackoverflow, you should be asking there.)
Can anyone explain to me what address(0) is in Solidity? I found the following in the docs but it doesn't really make sense to me:
If the target account is the zero-account (the account with the address 0), the transaction creates a new contract. As already mentioned, the address of that contract is not the zero address but an address derived from the sender and its number of transactions sent (the “nonce”). The payload of such a contract creation transaction is taken to be EVM bytecode and executed. The output of this execution is permanently stored as the code of the contract. This means that in order to create a contract, you do not send the actual code of the contract, but in fact code that returns that code.
http://solidity.readthedocs.io/en/develop/introduction-to-smart-contracts.html?highlight=address(0)#index-8
Within an Ethereum transaction, the zero-account is just a special case used to indicate that a new contract is being deployed. It is literally '0x0' set to the to field in the raw transaction.
Every Ethereum transaction, whether it's a transfer between two external accounts, a request to execute contract code, or a request to deploy a new contract, are encoded in the same way. A raw transaction object will look something like this:
transaction = {
nonce: '0x0',
gasLimit: '0x6acfc0', // 7000000
gasPrice: '0x4a817c800', // 20000000000
to: '0x0',
value: '0x0',
data: '0xfffff'
};
If to is set to something other than '0x0', this request will result in transferring ether to the address (if value is non-zero), and execute the function encoded in the data field. Remember, the address can either be a contract or an external account.
When the to address is the zero-address, a new contract will be created by executing the code in data (this is what is meant by "code that returns the code"). The address of the newly created contract is technically known beforehand as it's based on the address of the sender and it's current nonce. That address becomes the official address of the contract after mining.
For a pretty good read on Ethereum transactions, check out this blog post.
Note: There is also the actual Solidity code statement address(0) which is the initial value of a variable of type address. The documentation you posted, however, is referring to specifically when the to account address in a transaction is set to '0x0'.
It's not actually true that a contract creation transaction has a "to" field set to the zero address (meaning 0x00...000). This is an easy mistake to make (and I've made it too) as it is described that way in many resources.
The passage you cite from the Solidity docs were updated to state this:
If the target account is not set (the transaction does not have a
recipient or the recipient is set to null), the transaction creates a
new contract. As already mentioned, the address of that contract is
not the zero address but an address derived from the sender and its
number of transactions sent (the “nonce”).
So you can see they realized at some point that the recipient field should be empty. I've actually looked at serialized creation transactions and found 0x80 there instead of an RLP-ed zero address.
In fact, 0x80 is the RLP encoding of an empty byte array, which is what the Yellow Paper states is the recipient for a contract creation:
The address hash $T_t$ is slightly different: it is either a 20-byte
address hash or, in the case of being a contract-creation transaction
(and thus formally equal to ∅), it is the RLP empty byte sequence and
thus the member of $B_0$
As I said, this is a common source of confusion. In that vein, this GitHub PR rolling back a mistakenly "fixed" test is amusing. It has the comment:
RLP encoding of 0 is encoding of empty byte array, so 0x80 is correct.
0x00 is encoding of byte array of length 1 containing one byte 0, not
encoding of integer 0.