Solidity Tips – Part #1
Today I’m sharing two simple Solidity tips I came across while working on a personal project.
Let’s get right to it.
Forwarding encoded arguments
If you ever need to forward arguments from a function, you can combine them all into a struct and then encode the whole struct.
Then, on the receiving end, you just decode the struct, and you can read all arguments again.
I’ll explain with an example:
There are two contracts A and B. A is the caller, B is being called, B provides some feature. But B also requires A to implement a callback function. Now when A calls the function feature1 on B, inside B calls the callback in A. And during that callback, contract A needs to have some information about the operation, it needs to know what request this is about.
In this case, we need contract A to encode some arguments, add them to the call to feature1 and then feature1 forwards them in the callback back to A.
Contract B doesn’t care about this information, so we can encode a whole struct in A.main and then decode it in the A.callback.
Contract A
contract A {
public address contractB;
constructor(address b_){
contractB = b_;
}
struct CallbackInfo{
address sender,
uint256 amount
}
function callB(address token0, address token1, uint256 amount){
CallbackInfo memory extra = CallbackInfo({
sender: msg.sender,
amount: amount
});
bytes32 extraInfo = abi.encode(extra);
B(contractB).feature1(extraInfo);
}
function callback(bytes calldata data){
CallbackInfo memory extra = abi.decode(data, (CallbackInfo));
IERC20(token).transferFrom(extra.sender, msg.sender, extra.amount);
}
}
Contract B
contract B {
public address contractA;
function feature1(bytes extraInfo){
... some logic here
IContractA(msg.sender).callback(extraInfo);
... more logic here
}
}
This can be useful when we have Contract A as a “Manager” contract for our funds and contract B is an exchange pool or some other financial instrument we like to interact with but not give it any allowance. With this setup, during the callback, we can transfer tokens from A to B, and then B can continue with the rest of its logic.
Hash together multiple keys into one unique key
Hashing together a few values is a great way to generate unique keys for your mapping whose uniqueness is based on more than one value.
Let’s say you have a mapping that needs to store unique values based on owner address and token address, it’s an advanced balances mapping.
One way to do this is to have nested mapping like so.
mapping(address => mapping(address => uint256)) public balances;
This is a two level mapping where as the first address you could use the user address and the second will be the token address. But the same thing can be achieved with one simple mapping by creating a unique key out of the two addresses.
This is what it looks like:
contract MyToken {
mapping(bytes32 => uint256) public balances;
function setVal(address token, uint256 value) public {
bytes32 keyHash = keccak256(abi.encode(msg.sender, token));
balances[keyHash] = value;
}
}
We take the user address: msg.sender, and the token address and we encode, then hash these two addresses combined. This gives us one unique key for every user+token combination.
That’s all for today’s article, short and sweet.
Until next time.