I am implementing an on-chain font in Solidity. It works great, but I am not able to eliminate unnessary copies of bytes from code space (bytes constants) to the storage.
Here is the current implementation.
contract LondrinaSolid is IFontProvider {
uint constant fixed_height = 1024;
uint constant fixed_baseline = 805;
bytes constant font_0 = "\x4d\x60\xd1\x74\x05\x43\x8a\x56\x1f\x29\x56\x0b\xd2\x55\x21\x63\x50\x00\x00\x45\x62\x2e\x45\x4a\xc5\x45\xf4\x19\x45\xf5\x4b\x45\xf3\xb0\x45\xff\x65\x45\xfe\xeb\x55\x39\x2e\x56\x3b\x44\x55\x8b\x73\x65\x0c\x4c\x05\x73\xc0\x45\x02\xc5\x35\x97\x63\x50\x05\x95\x44\xfe\x21\x44\xb7\xcd\x03\x5a\x6d\x40\x75\x54\x06\x63\xfc\x54\x83\xe6\x54\x99\xc7\x54\x9a\xe0\x54\x01\xe3\x44\xf5\xd2\x44\xc9\xef\x44\xd5\xef\x44\x66\xef\x44\x66\x0b\x45\x72\x0b\x45\x3e\x40\x45\x40\x73\x50\x3d\x73\x55\x39\xf7\x05\x5a";
bytes constant font_1 = "\x4d\x50\xbc\x27\x06\x73\xf7\x54\xf4\xf5\x64\x27\xfe\x54\xc6\xfe\x54\xc6\x63\x50\x00\x00\x55\x4e\x0d\x55\x7c\x0c\x05\x73\x4e\x45\xfb\x4f\x45\xea\x05\x35\x8a\x08\x35\x61\x04\x35\xbd\xfe\x34\xaf\x63\x50\x00\x00\x45\xa1\xfa\x44\x7e\xfb\x04\x73\x9d\x54\x02\x99\x54\x01\x63\x50\x00\x00\x45\xbd\x64\x45\xac\x80\x05\x53\x09\x55\xfb\x0a\x65\x0d\x63\x50\x00\x00\x55\x1b\x14\x55\x4b\x1f\x05\x73\x46\x55\x0f\x4b\x55\x0f\x18\x45\xf9\x1c\x45\xed\x5a\x00";
bytes constant font_2 = "\x4d\x50\xd3\x57\x06\x73\x0a\x45\xa6\x18\x45\x93\x39\x45\xd6\x4b\x55\x0d\xfe\x54\x66\xcc\x54\x81\xb4\x54\x33\x7f\x54\x53\xb6\x54\x3b\xb2\x54\x41\xe8\x54\x24\xe3\x54\x4f\xf6\x54\xbb\xf5\x54\xc3\x63\x50\x00\x00\x55\x0f\x04\x55\x1e\x05\x05\x73\xbd\x45\xfb\xd4\x45\xfb\xee\x45\xfd\xf6\x45\xfd\x08\x45\xfa\x08\x45\xf7\xfd\x44\xb7\xff\x44\xab\xfd\x44\xcc\xf6\x44\xbc\x63\x50\x00\x00\x45\x04\x06\x35\xe9\x02\x55\x00\x00\x55\x23\xb5\x54\x48\x9f\x04\x73\x99\x45\xb4\xb9\x45\x90\x31\x45\xc5\x31\x45\x7f\xfc\x44\x56\xa4\x44\x14\xf2\x43\xe0\xc0\x53\x02\x53\x40\xf6\xbb\x55\x15\x58\x06\x63\x00\x55\x00\x39\x55\x15\xbe\x45\xff\x5a\x00";
...
bytes constant font_X = "\x4d\x50\x09\x35\x05\x73\x40\x55\x93\x4f\x55\xc7\x2f\x55\x94\x2e\x55\xad\xce\x54\xaa\xc8\x54\xbe\xcd\x54\xa5\xc9\x54\xad\x63\x50\x00\x00\x55\x28\x10\x55\x60\x0e\x05\x73\x50\x45\xfd\x50\x45\xfd\x63\x50\x00\x00\x55\x1e\xaf\x54\x25\x98\x04\x73\x1f\x45\x96\x1f\x45\x96\x63\x50\x00\x00\x55\x1c\x6a\x55\x20\x7a\x05\x73\x13\x55\x48\x19\x55\x51\x63\x50\x00\x00\x55\x30\x0e\x55\x5d\x09\x05\x73\x52\x55\x01\x57\x55\x02\x63\x50\x00\x00\x45\xe5\x9f\x44\xd8\x6e\x04\x73\xc5\x44\x3a\xc2\x44\x1d\x63\x50\x00\x00\x55\x34\x52\x54\x41\x29\x04\x73\x34\x45\x51\x34\x45\x51\x63\x50\x00\x00\x45\xb3\x01\x45\xa2\x01\x05\x73\xa1\x54\x03\x99\x54\x04\x63\x50\x00\x00\x45\xca\xc9\x45\xc5\xd0\x55\x00\x00\x45\xe3\xa5\x44\xe0\x93\x04\x73\xe8\x44\xa0\xe8\x44\xa0\x63\x50\x00\x00\x45\x76\xff\x44\x3b\x05\x05\x5a";
bytes constant font_Y = "\x4d\x50\x17\x30\x05\x73\x19\x55\x59\x30\x55\x8d\x57\x55\xdc\x57\x55\xe8\x63\x50\x00\x00\x45\xc5\x9e\x45\xbd\xb1\x05\x53\x12\x85\x1d\x10\x85\x22\x63\x50\x00\x00\x55\x42\x04\x55\x64\x03\x05\x73\x4a\x45\xfd\x58\x55\x01\x63\x50\x00\x00\x55\x3f\x56\x54\x55\x2e\x04\x53\xb6\x66\x15\xcb\x56\xe0\x73\x50\x39\x5f\x54\x3a\x51\x04\x63\x00\x55\x00\xb2\x44\xf2\x4c\x54\x07\x00\x55\x00\xbb\x54\xc7\xb9\x54\xcc\x00\x55\x00\xbe\x44\x48\xbc\x44\x34\x00\x55\x00\xeb\x44\xee\xab\x44\xf3\x73\x40\xc0\x08\x45\xa5\x07\x05\x5a";
bytes constant font_Z = "\x4d\x50\x0b\x33\x05\x73\x0a\x55\x33\x09\x55\x4f\x03\x55\x4b\x0d\x55\x54\x63\x50\x00\x00\x55\xeb\x06\x55\xfc\x0c\x55\x00\x00\x45\x8a\xa5\x45\x7a\xb9\x05\x53\x26\x75\x4a\x19\x75\x73\x63\x50\x00\x00\x55\x01\x44\x55\x01\x5c\x05\x73\x04\x55\x4e\x0c\x55\x58\x63\x50\x00\x00\x55\xab\xf8\x54\xcf\xf9\x04\x73\xdb\x55\x03\x06\x56\x07\x63\x50\x00\x00\x55\x0e\x6b\x54\x02\x46\x54\x00\x00\x45\x0c\xff\x34\xf6\x08\x55\x00\x00\x55\x92\x2d\x54\xa4\x14\x04\x73\x6a\x45\x64\x6d\x45\x59\x01\x45\xad\x02\x45\x9d\xff\x44\xb5\xfc\x44\xb0\x63\x50\x00\x00\x45\x35\xfd\x44\x12\xfc\x04\x73\x3f\x54\x01\xf7\x53\x09\x5a\x00";
mapping(uint => bytes) fontData;
mapping(uint => uint) widths;
constructor() {
registerAll();
}
function _register(string memory _char, bytes memory _bytecode, uint _width) internal {
uint key = uint(uint8(bytes(_char)[0]));
if (_bytecode.length > 0) {
fontData[key] = _bytecode;
}
widths[key] = _width;
}
function registerAll() external onlyOwner {
_register("0", font_0, 554);
_register("1", font_1, 414);
_register("2", font_2, 540);
...
_register("X", font_X, 531);
_register("Y", font_Y, 531);
_register("Z", font_Z, 531);
}
function height() external pure override returns(uint) {
return fixed_height;
}
function baseline() external pure override returns(uint) {
return fixed_baseline;
}
function widthOf(string memory _char) external view override returns(uint) {
uint key = uint(uint8(bytes(_char)[0]));
return widths[key];
}
function pathOf(string memory _char) external view override returns(bytes memory) {
uint key = uint(uint8(bytes(_char)[0]));
return fontData[key];
}
}
The problem is in the _register method, which stores the mapping between the charactor and the vector data in the "fontData". Even thought the font data itself is already in this smart contract, it copies it to the "memory" space when calling this method and copies it into the "storage", which is quite expensive (12M units of gas).
One possible work around is to eliminate the _register method, and implement a binary search in pathOf method to directly return font_X, but that code is ugly and not scalable.
Ideally, I just want to store the references to those font data in "fontData" and return them in pathOf method.
I'd appreciate any suggestions, including writing some assembly solution (which I am comfortable with).
I was able to solve this problem by creating a function for each font, and having a mapping from character codes to those functions in the storage.
With this change, I was able to reduce the deployment cost of one particular font from 0.13ETH to 0.027ETH (assuming 30gwei gas price).
Related
I want to create a time-based upkeep directly from my contract. I was able to register and fund the upkeep but for some reason the function is not getting executed automatically.
Here's the code
`
// Goerli network
address public cronFactoryAddress = 0x1af3cE8de065774B0EC08942FC5779930d1A9622;
address public keeperRegistrar = 0x57A4a13b35d25EE78e084168aBaC5ad360252467;
constructor(){
cronFactory = ICronFactory(cronFactoryAddress);
}
function createUpkeep(string memory _cronString) public{
address _target = address(this);
bytes memory functionToCall = bytes(abi.encodeWithSignature("sendSalary(string)", _cronString));
bytes memory job = cronFactory.encodeCronJob(_target, functionToCall, _cronString);
uint256 maxJobs = cronFactory.s_maxJobs();
address delegateAddress = cronFactory.cronDelegateAddress();
address newCronUpkeep = address(new CronUpkeep(msg.sender, delegateAddress, maxJobs, job));
allUpkeeps.push(newCronUpkeep);
}
function fundUpkeep(uint256 _linkAmount, address _upkeepAddress) public{
bytes4 reg = bytes4(keccak256("register(string,bytes,address,uint32,address,bytes,bytes,uint96,address)"));
bytes memory _data = abi.encode(
"TestV2",
"",
_upkeepAddress,
uint32(500000),
address(this),
"",
"",
_linkAmount,
address(this)
);
bytes memory combinedData = abi.encodePacked(reg, _data);
LinkContract.transferAndCall(keeperRegistrar, _linkAmount, combinedData);
}
sendSalary is the function in my contract that I want to be executed at regular intervals.
cronFactory is the cron factory contract.
cronUpkeep is the cronUpkeep.sol contract from the chainlink github repo.
To create these functions, I created a time-based upkeep manually and used the transaction logs to find what all function are being called and implemented the same here.
But, Once I execute both these functions nothing happens, however, I am able to find the upkeep registered on chainlink's website . And also it shows the trigger as custom trigger on upkeep page on chainlink:
chanlink upkeep
Please let me know how I can solve this? Any help would be appreciated. Thanks in advance
Contracts cannot execute themselves. Function needs to be called. While contract (function) is not called, contract is sleeping, because every time it makes operations, they should be payed (aka gas), so there is no way to throw an allways-active-timer inside of the contract (infinite gas). It means that you have to make calls manually or use automation services like ChainLink, Openzepplin Defender etc.
You can make a requirement by time-passed with
uint256 private lastTimeStamp;
uint256 private interval;
constructor() {
lastTimeStamp = block.timestamp;
interval = 7 days;
}
function isTimePassed() public view returns (bool timePassed) {
timePassed = ((block.timestamp - lastTimeStamp) > /*7 days */ interval);
return timePassed;
}
function smth() public {
(bool timePassed) = isTimePassed();
...
}
Something like this.
I changed the state variable in the constructor to a numeric value and printed the difference in gas consumed. Here's my code. The results showed that constructing the BeforeDemo consumed 93915 gas, constructing the AfterDemo consumed 76288 gas and the difference was 17627. I thought the difference came from one SLOAD but one SLOAD can't cost so much gas. Why is there so much difference between the two?
contract BeforeDemo {
uint256 public constant INITIAL_SUPPLY = 10000 * (10 ** 18);
constructor() {
check(INITIAL_SUPPLY);
}
function check(uint256 supply) public {
}
}
contract AfterDemo {
constructor() {
check(10000 * (10 ** 18));
}
function check(uint256 supply) public {
}
}
There is no SLOAD because constants are stored in the bytecode, not in storage. The difference in gas cos you see is due to the contract bytecode size.
When using uint256 public constant INITIAL_SUPPLY the compiler generates a public getter function for it:
function INITIAL_SUPPLY() public pure returns(uint256) { return 10000 * (10 ** 18) }
This means that more OPCODEs are run on contract deployment, resulting in a larger contract bytecode.
Contract deployment costs 32k gas + 200 per byte. On remix, I checked that BeforeDemo is 373 bytes long and AfterDemo is 284 bytes long.
(373 - 284) * 200 = 17800
So you get approx. 17800 gas difference on deployment because of the contract bytecode size.
I'm starting with contracts in solidity and a question arose, is there any way I can store addresses in the wallet so that only these stored can make the withdrawal of coins that I make available? Let me explain better, I want to airdrop for 1 month for example and after this period the people who got my coins can withdraw from my contract, but so that only the people who complied with the airdrop rules. I want to do this so they pay transaction fees. But I want only addresses authorized by the owner of the contract to be able to withdraw the currency
Another way of doing this, in order to avoid gas, is to either use merkle trees or signatures. I have implemented signatures in two NFT projects.
Basically, for every address you wish to whitelist, you sign a message offchain, by using a wallet in your possession (it can be empty, you only need to sign messages).
Example call for signing via JavaScript (by using the web3js lib):
web3.eth.accounts.sign(message, privateKey);
By using the privateKey, this allows you to sign multiple messages at once (without having to confirm in MetaMask. It is clear that: please keep that private key as safe as possible! Only execute this code locally on your machine! Do not store the privateKey in any text file, especially not in the project folder, that might get synched to github!
The other way of doing it via MetaMask:
web3.eth.personal.sign(message, from);
(where "from" is the address of the signer).
On the contract side, there are some functions that can be used to retrieve the signer of a message, by reconstructing the message and passing the signature you have generated.
Some reference code:
// used to keep track of issuer addresses, that can issue whitelists
mapping(address => bool) internal issuers;
// builds a prefixed hash to mimic the behavior of eth_sign.
function prefixed(bytes32 hash) internal pure returns(bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
// get the signer address by message and signature
function recoverSigner(bytes32 _message, bytes memory sig) internal pure returns(address){
uint8 v;
bytes32 r;
bytes32 s;
(v, r, s) = splitSignature(sig);
return ecrecover(_message, v, r, s);
}
function signerIsIssuer(bytes memory message, bytes memory _sig, uint256 nonce) public view returns(bool){
// check the issuer
if (!issuers[recoverSigner(message, sig)]) {
return false;
} else {
return true;
}
}
// splits the signature into its components
function splitSignature(bytes memory sig) internal pure returns(uint8, bytes32, bytes32){
require(sig.length == 65);
bytes32 r;
bytes32 s;
uint8 v;
assembly {
// first 32 bytes, after the length prefix
r:= mload(add(sig, 32))
// second 32 bytes
s:= mload(add(sig, 64))
// final byte (first byte of the next 32 bytes)
v:= byte(0, mload(add(sig, 96)))
}
return (v, r, s);
}
// this has to hash the same parameters as on the offchain side
// do not reuse nonces
function hashMessage(address authorizedWallet, uint256 nonce) public returns(bytes memory){
// create the message hash
bytes32 message = prefixed(keccak256(abi.encodePacked(authorizedWallet, nonce)));
if (signerIsIssuer(message, _sig, _nonce)){
// authorizedWallet really is authorized by signature
}
}
If you want to be able to remove authorization, then you either need to invalidate the nonce, or work with expiration timestamps or keep an address blacklist. Please note, that once a signature is emitted, if the contract has no ways to be told, that that signature is not valid anymore, the wallet address will be authorized forever.
Hope this helps!
code:
contract test{
address owner;
mapping(address=>bool) authorizedAddresses;
mapping(address=>bool) isRegistered;
mapping(uint256=>address) users;
uint256 userCounter;
constructor(){
owner = msg.sender;
}
modifier isOwner(){
require(msg.sender == owner, "Access denied!");
_;
}
function changeOwner(address _to) public isOwner{
owner = _to;
}
function authorizeAddress(address _address) public isOwner{
if(isRegistered[_address]){
authorizedAddresses[_address] = true;
}else{
users[userCounter] = _address;
isRegistered[_address] = true;
authorizedAddresses[_address] = true;
userCounter ++;
}
}
function unauthorizeAddress(address _address) public isOwner{
require(authorizedAddresses[_address], "User is not authorized already");
authorizedAddresses[_address] = false;
}
modifier isAuthorizedAddress(){
require(authorizedAddresses[_address], "You are not authorized to call this function");
_;
}
I should note I did not test this code and may throw error, so if it throws any error let me know and modify it or you can do it let me know how to fix it and edit the code for others.
If you did not understand any part, simply ask to describe :)
Best regards
You can use ERC20 token standart. Just define allowance for those addresses who complied with the airdrop rules.
As others wrote, you can store addresses in a mapping like mapping(address=>bool) authorizedAddresses;
However, it is expensive and inefficient to store hundreds of addresses on the blockchain. Think about that, probably you upload a bunch of addresses you don't even need to, because people will forget that they can get the airdrop and they won't even claim it.
A better approach is to give them signed coupons. When they claim the airdrop on your website, the transaction submits a coupon and your contract will verify if the coupon is:
signed by you
belongs to the sender address
allowed to claim the amount
It all happens in the background, so users won't notice anything.
Generate coupons in JS:
const {
keccak256,
toBuffer,
ecsign,
bufferToHex
} = require("ethereumjs-util")
const { ethers } = require('ethers')
const PRIV_KEY = "YOUR_SIGNER_PRIVATE_KEY"
//utils
function generateHashBuffer(typesArray, valueArray) {
return keccak256(
toBuffer(ethers.utils.defaultAbiCoder.encode(typesArray,
valueArray))
)
}
function createCoupon(hash, signerPvtKey) {
return ecsign(hash, signerPvtKey)
}
function fromHexString(hexString) {
return Uint8Array.from(Buffer.from(hexString, 'hex'))
}
function serializeCoupon(coupon) {
return {
r: bufferToHex(coupon.r),
s: bufferToHex(coupon.s),
v: coupon.v,
}
}
//generate coupon
function generate(claimAmount, address) {
const userAddress = ethers.utils.getAddress(address) // get checksum-correct address
const hashBuffer = generateHashBuffer(
["uint256", "address"],
[claimAmount, userAddress]
)
const coupon = createCoupon(hashBuffer, fromHexString(PRIV_KEY))
console.log({
address,
claimAmount,
coupon: serializeCoupon(coupon)
})
}
Now you have the coupon, you can build an API, so when the user click Claim on your website, he can submit the amount and the signature to the blockchain in the claim transaction.
Verify signature in your contract:
contract Airdrop is
Ownable,
ReentrancyGuard
{
address private COUPON_SIGNER = 0xYOUR_SIGNER_ADDRESS;
mapping(address => bool) public CLAIMED_ADDRESSES;
struct Coupon {
bytes32 r;
bytes32 s;
uint8 v;
}
// check that the coupon sent was signed by the admin signer address
function _isVerifiedCoupon(bytes32 digest, Coupon memory coupon)
internal
view
returns (bool)
{
address signer = ecrecover(digest, coupon.v, coupon.r, coupon.s);
require(signer != address(0), "ECDSA: Invalid whitelist signature.");
return signer == COUPON_SIGNER;
}
// Claim Airdrop
function claim(uint256 amountToClaim, Coupon memory coupon) external nonReentrant {
require(
CLAIMED_ADDRESSES[msg.sender] == false,
"This address already claimed a refund."
);
require(
address(this).balance >= amountToClaim,
"Contract balance is too low."
);
bytes32 digest = keccak256(abi.encode(amountToClaim, msg.sender));
require(
_isVerifiedCoupon(digest, coupon),
"Unexcepted error: unable to verify coupon."
);
CLAIMED_ADDRESSES[msg.sender] = true;
payable(msg.sender).transfer(amountToClaim); // Send ETH
}
}
PLEASE PLEASE don't just copy this code: TEST, TEST, and TEST. Always write unit tests.
I am trying to create mapping of type mapping(string => string) where the you store some text by keyed by its the string representation of its hash, but I've been stymied by the inability to take the calculated hash value and convert it to its string representation.
I tried the below, but it doesn't work. The hashing function appears to work, but the conversion to string doesn't. (Running function hash() returns an error which I don't really understand.
pragma solidity 0.8.4;
contract HashTextMap {
mapping(string=>string) textMap;
function set(string memory text) public {
bytes32 val;
val = sha256(abi.encodePacked(text));
string memory key = string(abi.encodePacked(val));
textMap[key] = text;
}
function get(string memory key) public view returns(string memory) {
return textMap[key];
}
function hash(string memory text) public pure returns(string memory) {
bytes32 val;
val = sha256(abi.encodePacked(text));
string memory key = string(abi.encodePacked(val));
return key;
}
}
Running this in the remix ide, the contract compiles and sets returns normally, but attempting I can't text get because I can't get the hash using hash() which produces this error.
{ "error": "Failed to decode output: null: invalid codepoint at offset 0; unexpected continuation byte (argument=\"bytes\", value={\"0\":153,\"1\":168,\"2\":124,\"3\":145,\"4\":53,\"5\":23,\"6\":70,\"7\":37,\"8\":43,\"9\":238,\"10\":126,\"11\":38,\"12\":250,\"13\":191,\"14\":48,\"15\":2,\"16\":61,\"17\":234,\"18\":227,\"19\":36,\"20\":138,\"21\":6,\"22\":125,\"23\":166,\"24\":226,\"25\":63,\"26\":146,\"27\":129,\"28\":199,\"29\":135,\"30\":194,\"31\":139}, code=INVALID_ARGUMENT, version=strings/5.4.0)" }
This should help you
Solidity: How to represent bytes32 as string
The problem is that currently you are trying to convert your hash to a utf-8 string. The hash has values that aren't supported by utf-8. I think what you meant to do, was represent your hash as a string.
I am trying to write string to the blockchain using events. This will cost a lot of gas regularly, so I am attempting to compress my strings. They become compressed into a uint8array in js. Here is my solidity script:
pragma solidity ^0.4.18;
contract EthProj {
event Message(uint8[] message, address add, uint256 cost);
event Username(uint8[] name, address add, uint256 cost);
function setMessage(uint8[] _fMessage) public {
uint8[] memory output = new uint8[](_fMessage.length);
output = _fMessage;
emit Message(output, msg.sender, gasleft());
}
function setUsername(uint8[] _userName) public {
emit Username(_userName, msg.sender, gasleft());
}
}
My goal with this is to have the size of the array be dependent on the size of the compressed text, but I get invalid number of arguments error when calling it using this:
message = document.getElementById("MessageBox").value;
compressed = shoco.compress(message);
EthProj.setMessage.sendTransaction(compressed, {from: document.getElementById("add").value});`
Can you not make an array with variable size, and if I can't, than how do I go about achieving my goal? The error is: Invalid number of arguments to solidity function