# Gas optimisation for my art experiment spacecans.com

so I was microdosing some research chems and the thought came to me that I need to do some artsy blockchain stuff for once. It's two tin cans floating in space that you can advertise your so-called shitcoins or whatever else on. It's currently running on the Rinkeby testnet. You can buy parts of the labels of these tin cans with Ether, put a custom image on them, and link it to a website.

However my smart contract still needs some optimisation regarding gas and security. Currently the largest area one can buy in one transaction is 8x8 parts out of 32x32 total, because the buy function apparently consumes too much gas otherwise. (is this because of the block size limit?)

Any tips for a solidity newbie regarding gas optimisation? Thanks!

Here's my smart contract on etherscan: https://rinkeby.etherscan.io/address/0x51d40569c946355b8e6c72ef0bcc85991ec273d2#code

Here's the website: https://spacecans.com

``````pragma solidity >=0.4.22 <0.6.0;

contract Main {

uint8 public constant N = 32;
uint8 public constant M = 32;
uint256 public constant initialTabPrice = 1 finney;
uint256 public constant feePercent = 15;
uint256 public constant referralPercent = 30;
uint256 public constant appreciationPercent = 200;
Tab[N][M] public tabs;

struct Tab {
uint256 price; /// Price for next purchase
bytes32 imageUrl;

/// Used for images spanning over multiple tabs with dimensions X,Y
uint8 x; /// x index [0, X-1]
uint8 X;
uint8 y; /// y index [0, Y-1]
uint8 Y;
}

struct ToPay {
uint256 amount;
}

constructor() public {
chairperson = msg.sender;
}

function uint2str(uint i) internal pure returns (string memory){
if (i == 0) return "0";
uint j = i;
uint length;
while (j != 0){
length++;
j /= 10;
}
bytes memory bstr = new bytes(length);
uint k = length - 1;
while (i != 0){
bstr[k--] = byte(uint8(48 + i % 10));
i /= 10;
}
return string(bstr);
}

function bytes32ToString(bytes32 x) internal pure returns (string memory) {
bytes memory bytesString = new bytes(32);
uint charCount = 0;
for (uint j = 0; j < 32; j++) {
byte char = byte(bytes32(uint(x) * 2 ** (8 * j)));
if (char != 0) {
bytesString[charCount] = char;
charCount++;
}
}
bytes memory bytesStringTrimmed = new bytes(charCount);
for (uint j = 0; j < charCount; j++) {
bytesStringTrimmed[j] = bytesString[j];
}
return string(bytesStringTrimmed);
}

function getTabs(uint8 x1, uint8 y1, uint8 x2, uint8 y2) external view returns(string memory) {
string memory res = "";
for (uint8 yt = y1; yt <= y2; yt++) {
for (uint8 xt = x1; xt <= x2; xt++) {
res = string(abi.encodePacked(res, ",", uint2str(getTabPrice(xt,yt)),  ",", bytes32ToString(tabs[xt][yt].linkUrl), bytes32ToString(tabs[xt][yt].linkUrl), ",", bytes32ToString(tabs[xt][yt].imageUrl), bytes32ToString(tabs[xt][yt].imageUrl), bytes32ToString(tabs[xt][yt].imageUrl), "#", uint2str(tabs[xt][yt].x), ".", uint2str(tabs[xt][yt].X), ".", uint2str(tabs[xt][yt].y), ".", uint2str(tabs[xt][yt].Y)));
}
}
return res;
}

function getTabPrice(uint8 x, uint8 y) internal view returns(uint256) {
if (tabs[x][y].price == 0) {
return initialTabPrice;
}
return tabs[x][y].price;
}

function getTabOwner(uint8 x, uint8 y) internal view returns(address payable) {
if (tabs[x][y].price == 0) {
return chairperson;
}
return tabs[x][y].owner;
}
// string calldata linkUrl, string calldata imageUrl, string calldata referral

function buyTabs(uint8 calldata z, bytes32 calldata s) external payable {
/// z: x1, y1, x2, y2
/// Calculate combined price for asked tabs
uint8 xt;
uint8 yt;
uint256 price = 0;
uint256 tabPrice;

for (xt = z; xt <= z; xt++) {
for (yt = z; yt <= z; yt++) {
price += getTabPrice(xt,yt);
}
}

/// Check if payment is sufficient
assert(msg.value >= price);

ToPay memory toPay;

/// Pay previous owner and assign new owner and new price
for (xt = z; xt <= z; xt++) {
for (yt = z; yt <= z; yt++) {
tabPrice = getTabPrice(xt,yt);
tabOwner = getTabOwner(xt,yt);

//tabOwner.transfer((tabPrice * (100-feePercent)) / 100);
for(uint i=0; i<=toPay.length;i++) {
if (toPay[i].amount == 0) {
//toPay[i] = ToPay(tabOwner, (tabPrice * (100-feePercent)) / 100);
toPay[i].owner = tabOwner;
toPay[i].amount = (tabPrice * (100-feePercent)) / 100;
break;
}

if (toPay[i].owner == tabOwner) {
toPay[i].amount += (tabPrice * (100-feePercent)) / 100;
break;
}
}

tabs[xt][yt] = Tab(msg.sender, tabPrice * appreciationPercent / 100, [s, s] , [s, s, s], xt-z, z-z+1, yt-z, z-z+1);
}
}

for(uint i=0; i<=toPay.length;i++) {
if (toPay[i].amount == 0) {
break;
}
toPay[i].owner.transfer(toPay[i].amount);
}

if (s != 0) {
}

/// Remaining funds are fees
}

}

function retrieveLostEther() public {
assert(msg.sender == chairperson);
• Data structure such as `mapping` is much more gas-friendly than looping through an array so probably the answerer means you should consider changing the data structure to use more mappings, for example. Or other more efficient structures. Jun 23 '20 at 12:32