Yul: How to Concatenate String in Solidity Inline Assembly & How Much Gas Savings
String is often a very tricky data type to work with in Solidity as it consists and many other fields such as the identifier and the length of it. As string can go beyond 32 bytes, similarly to array, it is necessary to ensure the entire thread of strings are being stored and presented.
Concatenation method is not available in Solidity inline assembly but it can be done by the manual displacement of the memory blocks. Below is how traditional concatenation works in Solidity:
function concatTraditional() public pure returns(string memory) {
string memory A = "Hello ";
string memory B = "world";
return string.concat(A, B);
}
Without optimisation, this contract cost 196,783 gas and running it cost 1,127 gas.
Below is how it is done with Yul (with explanation through commenting):
function concatInAssembly() public pure returns(string memory) {
string memory A = "Hello ";
string memory B = "world";
// Strings can be define through function of parameters
assembly {
let strA := mload(add(A, 0x20))
let strB := mload(add(B, 0x20))
// Remember, the first position of string is the length of it so
// the content will only be after the first memory block and that
// is why we have to add 32 bytes to it.
// If you are dealing with a dynamic string that could be longer
// than 32 bytes, you will need to use a loop to go through it.
mstore(0x80, 0x20)
// I directly use 0x80 without the free memory pointer as there are
// no other calling that requires it. I have loaded string A & B
// into the variables so they are in the stack instead.
// In the above line, the first return message for string will
// always be 0x20. This is the identifier for string.
mstore(0xa0, add(mload(A), 0x20))
// This second memory will be length of the string. Numerically,
// "Hello world" is only 11 characters so it should be just 0xb.
// However as we are doing block concatenation, the entire block
// have to be factored in so is 0x20 (regardless the length) plus
// the number of characters for "world".
mstore(0xc0, strA)
mstore(0xe0, strB)
// Now add the strings accordingly. If you have more than 2 strings,
// fill the position accordingly or you can use a loop to fill them.
return(0x80, 0x80)
// Let's recap, the first position is 0x20 which is the fix definition
// for string. The second position is the length of the final. the
// third and forth position is the strings to be concatenated.
// There are total of 4 memory blocks and therefore the first
// return parameter is the beginning of the string and the length
// to be returned. If you have more blocks to be concatenated,
// add the 2nd parameter accordingly.
}
}
Without optimisation, this contract cost 156,783 gas and running it cost 344 gas.
The full string function will definitely cost more but it is good that in assembly we can manipulate to what we need and omit other unncessary functions. The traditional way to concatenate string is sure more neater and understandable as compared to the Yul way.
Concatenating string is usually being used in the tokenURI() method that is a standard function in the NFT contract. Some developers store their metadata in an IPFS directory and the IPFS link is the same.
E.g. ipfs://QmABCxyz/1.json
So you have the token ID of 1 and you need to concatenate "ipfs://QmABCxya/" + tokenId + ".json". TokenId can be converted from uint256 to string (which I will be sharing it in my next post) and be concatenated the same.
Do share feedback with me and any question you may encounter.
Comments
Post a Comment