Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky.
- Contract name:
- Reader
- Optimization enabled
- true
- Compiler version
- v0.8.25+commit.b61c2a91
- Optimization runs
- 1000
- EVM Version
- cancun
- Verified at
- 2024-09-19T00:14:41.598514Z
Constructor Arguments
0x000000000000000000000000bab99bdbc920ec9d0843993e98c11d7e482814b7
Arg [0] (address) : 0xbab99bdbc920ec9d0843993e98c11d7e482814b7
contracts/Reader.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;
import {LibMulticaller} from "multicaller/src/LibMulticaller.sol";
import {SSTORE2} from "solady/src/utils/SSTORE2.sol";
import {IRandom} from "./implementations/IRandom.sol";
import {Errors, Ok, Reveal} from "./Constants.sol";
import {PreimageLocation} from "./PreimageLocation.sol";
error Misconfigured();
error IndexOutOfBounds();
contract Reader {
using SSTORE2 for address;
using PreimageLocation for PreimageLocation.Info;
uint256 internal constant ZERO = 0;
uint256 internal constant ONE = 1;
uint256 internal constant EIGHT = 8;
uint256 internal constant ONE_SIX = 16;
uint256 internal constant THREE_TWO = 32;
uint256 internal constant FOUR_EIGHT = 48;
uint256 internal constant NINE_SIX = 96;
uint256 internal constant TWO_FIVE_FIVE = 255;
address internal rand;
constructor(address _rand) payable {
rand = _rand;
}
/**
* signal publicly that these sections are still active
* @param infos the sections that you wish to signal are still active
* @dev best to do this on a regular interval such as after a hiatus,
* a missed cast, or if no activity occurs after a week
*/
function ok(PreimageLocation.Info[] calldata infos) external payable {
unchecked {
address provider = LibMulticaller.senderOrSigner();
uint256 len = infos.length;
uint256 i;
do {
if (infos[i].provider != provider) {
revert Errors.SignerMismatch();
}
emit Ok(provider, infos[i].section());
++i;
} while (i < len);
}
}
/**
* retrieve the address that contains preimage bytes
* @param info the location of a preimage sstore2 contract on chain
*/
function _pointer(
PreimageLocation.Info calldata info
) internal view returns (address) {
address pntr = IRandom(rand).pointer(info);
if (pntr == address(0)) {
revert Misconfigured();
}
uint256 size;
assembly {
size := extcodesize(pntr)
}
if (info.index > ((size / THREE_TWO) - ONE)) {
revert IndexOutOfBounds();
}
return pntr;
}
/**
* read the bytes held in a preimage section
* @param info the location of a preimage on chain
*/
function pointer(
PreimageLocation.Info calldata info
) external view returns (bytes memory) {
return _pointer(info).read();
}
/**
* get a series of bytes containing bit flags denoting which indicies of a given section have been consumed
* @param section the section in question (info where index can be set to zero)
* @return len the length in preimages of the section
* @return indices a series of bytes containing true bits denoting which indicies,
* from left to right, that contain consumed preimages
* @dev note that this is a very costly function with (at current chain state) up to 767 external calls
*/
function consumed(
PreimageLocation.Info calldata section
) external view returns (uint256 len, bytes memory indices) {
unchecked {
bytes memory data = _pointer(section).read();
len = data.length / THREE_TWO;
indices = new bytes(
(len / EIGHT) + (((len % EIGHT) > ONE) ? ONE : ZERO)
);
uint256 i;
uint256 seven = EIGHT - ONE;
do {
PreimageLocation.Info memory nfo = section;
nfo.index = i;
if (IRandom(rand).consumed(nfo)) {
indices[i / EIGHT] = bytes1(
uint8(
// take the current block of bits
uint256(uint8(indices[i / EIGHT])) |
// and add a one at the appropriate offset index
(ONE << (seven - (i % EIGHT)))
)
);
}
++i;
} while (i < len);
}
}
/**
* read a preimage's 32 bytes
* @param info the location of the preimage to read
*/
function at(
PreimageLocation.Info calldata info
) external view returns (bytes32) {
return _at(info);
}
function _at(
PreimageLocation.Info calldata info
) internal view returns (bytes32) {
return
bytes32(
_pointer(info).read(
info.index * THREE_TWO,
info.index * THREE_TWO + THREE_TWO
)
);
}
}
contracts/Constants.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;
event Ok(address indexed provider, bytes32 section);
event Bleach(address indexed provider, bytes32 section);
event Reprice(address indexed provider, uint256 pricePer);
event Ink(address sender, address indexed provider, bytes32 section, uint256 offset, address pointer);
event Heat(address indexed provider, bytes32 section, uint256 index);
event Start(address indexed owner, bytes32 key); // no need to index because all keys should be unique
event Link(address indexed provider, bytes32 location, bytes32 formerSecret);
event Reveal(address indexed provider, bytes32 location, bytes32 formerSecret);
event Expired(bytes32 key);
event Cast(bytes32 key, bytes32 seed);
event Chop(bytes32 key);
abstract contract Errors {
error DeploymentFailed();
error Misconfigured();
error UnableToService();
error MissingPayment();
error SecretMismatch();
error ZeroSecret();
error NotInCohort();
error SignerMismatch();
}
contracts/Consumer.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;
// import {console} from "hardhat/console.sol";
import {SSTORE2} from "solady/src/utils/SSTORE2.sol";
import {LibPRNG} from "solady/src/utils/LibPRNG.sol";
import {SafeTransferLib} from "solady/src/utils/SafeTransferLib.sol";
import {EfficientHashLib} from "solady/src/utils/EfficientHashLib.sol";
import {LibMulticaller} from "multicaller/src/LibMulticaller.sol";
import {IRandom} from "./implementations/IRandom.sol";
import {PreimageLocation} from "./PreimageLocation.sol";
import {ERC20} from "solady/src/tokens/ERC20.sol";
import {Errors} from "./Constants.sol";
error SecretMismatch();
event Undermine(uint256 id, bytes32 preimage);
event Chain(bytes32 indexed owner, uint256 id, bytes32 key);
event ConsumerReveal(uint256 id, bytes32 formerSecret);
contract Consumer {
using EfficientHashLib for bytes32;
using SafeTransferLib for address;
uint256 internal constant ZERO = 0;
uint256 internal constant ONE = 1;
uint256 internal constant NINE_SIX = 96;
uint256 internal constant ONE_SIX_ZERO = 160;
bytes32 internal immutable PREIMAGE_ZERO;
address internal immutable rand;
uint256 internal _id;
struct Link {
uint256 id;
address owner;
bool underminable;
bytes32 key;
bytes32 preimage;
bytes32 revealed;
}
constructor(address _rand) payable {
rand = _rand;
PREIMAGE_ZERO = bytes32(ZERO).hash();
}
mapping(bytes32 preimage => bytes32 formerSecret) internal _preimageToSecret;
mapping(bytes32 preimage => uint256 id) internal _preimageToId;
mapping(uint256 id => bytes32 owner) internal _owner;
mapping(uint256 id => bytes32 preimage) internal _preimage;
mapping(uint256 id => bytes32 key) internal _key;
function _undermineExpired(uint256 id, bytes32 hashed, bytes32 seed) internal {
// order preimage cannot be overriden until after all secrets have been revealed
// this creates a high incentive for both player 1, and rule enforcer to get secrets on chain
// before the expired line is crossed. either:
// 1) player 1 wins, and they want to claim their winnings (high incentive to keep secret safe)
// 2) player 1 loses, so the rule enforcer is incented to claim their winnings
// 3) if either one waits too long - and allows others overwrite the preimage,
// then the benefiting party risks a re-roll of the randomness seed
unchecked {
if (seed == bytes32(ZERO)) {
return;
}
if (hashed == _preimage[id]) {
return;
}
if (uint256(_owner[id] >> ONE_SIX_ZERO) == ZERO) {
revert Errors.Misconfigured();
}
// originator of the chained secret+preimage can reject updates
// it is up to anyone who would wish to turn this feature on to check that it will work ahead of time
// we allow non secret holdes to update the order preimage in order to maximally incent
// randomenss campaign completion
// think of it like chips with an expiry time. you might be able to cash them in,
// but the desk might also refuse to honor them if the expiry time is too far from the defined values
// in that case, they are worthless
// if a casino wants to have an intermediate period they can enforce that in their own contract
emit Undermine({id: id, preimage: hashed});
_preimage[id] = hashed;
// note that the preimage may not be what was originally intended - we do not track in the contract
}
}
/**
* @param id the id of the chained randomness to reveal
* @dev calling tell should be considered risky in that it will revert if you are
* a) do not have the original secret
* b) unable to set the preimage to the hash of your revealed secret because you were too late
* either way, at the end of this function call, you should have a preimageToSecret
* that is set so that you can use it (it will not be bytes32(0))
*/
function tell(uint256 id, bytes32 revealedSecret) external {
unchecked {
if (_preimageToSecret[_preimage[id]] != bytes32(ZERO)) {
return;
}
IRandom.Randomness memory r = IRandom(rand).randomness(_key[id]);
bytes32 hashed = revealedSecret.hash();
if (IRandom(rand).expired({timeline: r.timeline})) {
_undermineExpired({
id: id,
hashed: hashed,
seed: r.seed
});
}
if (hashed != _preimage[id]) {
// console.log(id);
// console.logBytes32(hashed);
// console.logBytes32(_preimage[id]);
revert SecretMismatch();
}
_preimageToSecret[hashed] = revealedSecret;
emit ConsumerReveal({id: id, formerSecret: revealedSecret});
// we do not emit an event here because it is more likely that users will simply
// do it themselves via a contract or only care about the latest
}
}
function chain(address owner, bool onlySameTx, bool underminable, bytes32 preimage)
external
payable
returns (uint256 id)
{
bytes32 key = IRandom(rand).latest(owner, onlySameTx);
if (key == bytes32(ZERO)) {
revert Errors.Misconfigured();
}
return _chainTo({
owner: LibMulticaller.senderOrSigner(),
underminable: underminable,
preimage: preimage,
key: key
});
}
function chainTo(address owner, bool underminable, bytes32 preimage, bytes32 key)
external
payable
returns (uint256)
{
return _chainTo({
owner: owner,
underminable: underminable,
preimage: preimage,
key: key
});
}
function latestId() external view returns (uint256) {
return _id;
}
function link(uint256 idParam) external view returns (Link memory l) {
bytes32 key = _key[idParam];
if (key == bytes32(ZERO)) {
return l;
}
bytes32 preimage = _preimage[idParam];
bytes32 o = _owner[idParam];
l = Link({
id: idParam,
key: key,
owner: address(bytes20(o << NINE_SIX)),
underminable: o >> ONE_SIX_ZERO == 0x00 ? false : true,
preimage: preimage,
revealed: _preimageToSecret[preimage]
});
}
function _chainTo(address owner, bool underminable, bytes32 preimage, bytes32 key) internal returns (uint256 id) {
unchecked {
if (preimage == bytes32(ZERO) || preimage == PREIMAGE_ZERO) {
revert Errors.Misconfigured();
}
id = _preimageToId[preimage];
bytes32 o = bytes32((underminable ? (ONE << ONE_SIX_ZERO) : ZERO) | uint256(uint160(owner)));
if (_preimage[id] == preimage) {
if (_owner[id] == o) {
return id;
}
}
id = ++_id;
_owner[id] = o;
_preimage[id] = preimage;
_key[id] = key;
// allow for reverse lookup
_preimageToId[preimage] = id;
emit Chain({owner: o, id: id, key: key});
}
}
}
contracts/FundedConsumer.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;
import {Consumer} from "./Consumer.sol";
import {Random} from "./Random.sol";
contract FundedConsumer {
Consumer immutable consumer;
Random immutable random;
constructor(address _consumer, address _random) payable {
consumer = Consumer(_consumer);
random = Random(_random);
}
}
contracts/PreimageLocation.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;
import {EfficientHashLib} from "solady/src/utils/EfficientHashLib.sol";
library PreimageLocation {
struct Info {
address provider;
bool callAtChange;
bool durationIsTimestamp;
uint256 duration;
address token;
uint256 price;
uint256 offset;
uint256 index;
}
using PreimageLocation for Info;
using PreimageLocation for bytes32;
using EfficientHashLib for bytes32;
/**
* derive a unique location hash that is hash(section + index)
* @param info location info to help derive hashes
*/
function location(Info memory info) internal pure returns (bytes32) {
return info.section().location(info.index);
}
function location(
bytes32 sec,
uint256 index
) internal pure returns (bytes32) {
return sec.hash(bytes32(index));
}
function section(Info memory info) internal pure returns (bytes32) {
unchecked {
return
EfficientHashLib.hash(
bytes32(uint256(uint160(info.provider))),
bytes32(info.encodeToken()),
bytes32(info.price),
bytes32(info.offset)
);
}
}
function encodeToken(Info memory info) internal pure returns (uint256) {
return
(uint256(info.durationIsTimestamp ? 1 : 0) << 255) |
(uint256(info.callAtChange ? 1 : 0) << 254) |
(uint256((uint40(info.duration) << 1) >> 1) << 160) |
uint256(uint160(info.token));
}
}
contracts/Random.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;
// import {console} from "hardhat/console.sol";
import {SSTORE2} from "solady/src/utils/SSTORE2.sol";
import {LibPRNG} from "solady/src/utils/LibPRNG.sol";
import {LibBitmap} from "solady/src/utils/LibBitmap.sol";
import {SafeTransferLib} from "solady/src/utils/SafeTransferLib.sol";
import {EfficientHashLib} from "solady/src/utils/EfficientHashLib.sol";
import {LibMulticaller} from "multicaller/src/LibMulticaller.sol";
import {IRandom} from "./implementations/IRandom.sol";
import {PreimageLocation} from "./PreimageLocation.sol";
import {Errors, Cast, Reveal, Link, Ink, Heat, Start, Expired, Chop, Bleach} from "./Constants.sol";
import {StorageSlot} from "./StorageSlot.sol";
import {SlotDerivation} from "./SlotDerivation.sol";
import {ConsumerReceiver} from "./implementations/ConsumerReceiver.sol";
contract Random is IRandom {
// this error is used inside of sstore to so we surface it here so that it sticks in the abi
using SSTORE2 for address;
using SSTORE2 for bytes;
using SafeTransferLib for address;
using StorageSlot for bytes32;
using StorageSlot for StorageSlot.Bytes32SlotType;
using SlotDerivation for *;
using LibPRNG for LibPRNG.PRNG;
using EfficientHashLib for bytes32;
using EfficientHashLib for bytes32[];
using PreimageLocation for PreimageLocation.Info;
using PreimageLocation for bytes32;
using LibBitmap for LibBitmap.Bitmap;
string private constant _NAMESPACE = "random";
mapping(address account => bytes32 latest) internal _latest;
mapping(address account => mapping(address token => uint256 amount))
internal _custodied;
mapping(address provider => mapping(uint256 token => mapping(uint256 price => uint256 max)))
internal _preimageCount;
mapping(address provider => mapping(uint256 token => mapping(uint256 price => mapping(uint256 offset => address pointer))))
internal _pointers;
mapping(address provider => mapping(uint256 token => mapping(uint256 price => LibBitmap.Bitmap bitmap)))
internal _accessFlags;
mapping(address provider => mapping(uint256 token => mapping(uint256 price => mapping(uint256 index => bytes32 formerSecret))))
internal _linkedSecret;
mapping(address provider => mapping(uint256 token => mapping(uint256 price => mapping(uint256 index => bytes32 formerSecret))))
internal _revealedSecret;
mapping(bytes32 key => bool chopped) internal _chopped;
mapping(address account => mapping(bytes32 txHash => bytes32 latest))
internal _latestInTx;
/**
* start the process to reveal the ink that was written (using invisible ink as a visual analogy)
* @dev the reason why this method uses flags (256 per slot) is because this allows a
* central entity to benefit from requesting randomness such as an eip3074 enabled multicaller
* and benefit greatly from the gas savings of access the same slot up to 256 times
* @dev notice that the index is derived from the preimage key by adding [95..16] and [15..1] together
*/
function _ignite(
PreimageLocation.Info memory info,
bytes32 section
) internal returns (bool) {
unchecked {
uint256 encodedToken = info.encodeToken();
if (_consumed({info: info, encodedToken: encodedToken})) {
return false;
}
_accessFlags[info.provider][encodedToken][info.price].set(
info.offset + info.index
);
emit Heat({
provider: info.provider,
section: section,
index: info.offset + info.index
});
return true;
}
}
/**
* check if the preimage has been consumed by randomness
* @param info the location of a preimage
* @param encodedToken the encoded token information (durationIsTimestamp[0,1),duration[56,95),token[96,255])
*/
function _consumed(
PreimageLocation.Info memory info,
uint256 encodedToken
) internal view returns (bool) {
if (
_pointerSize({info: info, encodedToken: encodedToken}) /
THREE_TWO <=
info.index
) {
revert Errors.Misconfigured();
}
// returning zero means that the secret has not been requested yet on chain
return
_accessFlags[info.provider][encodedToken][info.price].get(
info.offset + info.index
);
}
/**
* gets the number of bytes held by the pointer - a contract generated from preimage bytes
* @param info the preimage location
* @dev the index is not used for this so it can be set to 0
* @param encodedToken the encoded token information (durationIsTimestamp[0,1),duration[56,95),token[96,255])
*/
function _pointerSize(
PreimageLocation.Info memory info,
uint256 encodedToken
) internal view returns (uint256 size) {
address pntr = _pointers[info.provider][encodedToken][info.price][
info.offset
];
if (pntr == address(0)) {
revert Errors.Misconfigured();
}
assembly {
size := extcodesize(pntr)
}
size -= ONE;
}
/**
* write the secret to storage and emit an event
* @param info the location of the preimage that is being revealed
* @param formerSecret the former secret - this value, when run through keccak256 must match the preimage
* @return location the location hash of the preimage location info - this is used to create the randomness key
* @return first whether or not this was the first time that _flick was run successfully for this preimage location
* @dev this method will fail if an invalid info is passed and there is no pointer (storage to check against)
* @dev this method will fail if the secret does not match the stored preimage
*/
function _flick(
PreimageLocation.Info calldata info,
bytes32 formerSecret
) internal returns (bytes32 location, bool first) {
unchecked {
// length check is skipped because if one goes out of bounds you either err
// or you end up with zero bytes, which would be quite the feat to find the hash for
// always read 32 bytes
if (formerSecret.hash() != _toPreimage(info)) {
revert Errors.SecretMismatch();
}
uint256 tkn = info.encodeToken();
// only ever set once but do not penalize for lack of coordination
location = info.location();
(bytes32 linkedSecret, ) = _secret(info, tkn);
if (linkedSecret == bytes32(ZERO)) {
_linkedSecret[info.provider][tkn][info.price][
info.offset + info.index
] = formerSecret;
// gives provider access to staked tokens
_custodied[info.provider][info.token] += info.price;
emit Link({
provider: info.provider,
location: location,
formerSecret: formerSecret
});
return (location, true);
}
return (location, false);
}
}
function _toPreimage(
PreimageLocation.Info calldata info
) internal view returns (bytes32) {
uint256 tkn = info.encodeToken();
address pntr = _pointers[info.provider][tkn][info.price][info.offset];
if (pntr == address(0)) {
revert Errors.Misconfigured();
}
// length check is skipped because if one goes out of bounds you either err
// or you end up with zero bytes, which would be quite the feat to find the hash for
// always read 32 bytes
return
bytes32(
pntr.read(
(info.index * THREE_TWO),
((info.index * THREE_TWO) + THREE_TWO)
)
);
}
function reveal(
PreimageLocation.Info calldata info,
bytes32 formerSecret
) external payable {
if (_toPreimage(info) == keccak256(abi.encode(formerSecret))) {
// this event is the same one used during cast
// but it should not be used as a signal that the randomness has been cast
// only the cast event should be used for that
_revealedSecret[info.provider][info.encodeToken()][info.price][
info.offset + info.index
] = formerSecret;
emit Reveal({
provider: info.provider,
location: info.location(),
formerSecret: formerSecret
});
}
}
enum CastState {
SCATTERED,
SEED_SET,
MISSING_SECRET
}
/**
* accesses the stored secret
* @param info the info to access the location
* @param encodedToken encoded token info
*/
function _secret(
PreimageLocation.Info calldata info,
uint256 encodedToken
) internal view returns (bytes32 linkedSecret, bytes32 revealedSecret) {
linkedSecret = _linkedSecret[info.provider][encodedToken][info.price][
info.offset + info.index
];
if (linkedSecret == bytes32(ZERO)) {
revealedSecret = _revealedSecret[info.provider][encodedToken][
info.price
][info.offset + info.index];
} else {
revealedSecret = linkedSecret;
}
}
/**
* distribute tokens to a recipient
* @param recipient the recipient of the tokens
* @param token the token to send
* @param amount the number of tokens
*/
function _distribute(
address recipient,
address token,
uint256 amount
) internal {
if (amount == ZERO) return;
if (token == address(0)) {
recipient.safeTransferETH(amount);
} else {
token.safeTransfer(recipient, amount);
}
}
/**
* reverse previous charges toward the owner. owner can end up with up to 2x the amount they put in and
* will have to handle distributing tokens according to their own policy
* @param timeline the timeline of the randomness which holds the owner and whether a call should be triggered
* @param key the randomness key
* @param token the token being reversed
* @param payout the amount of the token being reversed
*/
function _reverseCharges(
uint256 timeline,
bytes32 key,
address token,
uint256 payout
) internal {
_custodied[address(uint160(timeline >> NINE_SIX))][token] += payout;
if (_shouldCall(timeline)) {
address(uint160(timeline >> NINE_SIX)).call(
abi.encodeWithSelector(
ConsumerReceiver.onReverse.selector,
key,
token,
payout
)
);
}
}
/**
* receive a number of tokens and attribute them to an account
* @param account the account to attribute tokens to
* @param token the tokens being received
* @param amount the number of tokens being received
*/
function _receiveTokens(
address account,
address token,
uint256 amount
) internal returns (uint256) {
unchecked {
if (token == address(0)) {
if (amount > msg.value) {
revert Errors.MissingPayment();
}
amount = msg.value;
} else {
// because we do not check balanceof delta, we will
// not correctly attribute tax/reflection tokens
uint256 before = token.balanceOf(address(this));
token.safeTransferFrom2(account, address(this), amount);
amount = token.balanceOf(address(this)) - before;
}
return amount;
}
}
/**
* decrement a desired amount from the provided account's token balance
* @param account the address to decrement against
* @param token the token balance to decrement
* @param desired the desired decrementation
* @return delta may be less than the desired. note that the amount
* actually decremented may be less than the desired input
*/
function _decrementTokenAmount(
address account,
address token,
uint256 desired
) internal returns (uint256 delta) {
unchecked {
uint256 limit = _custodied[account][token];
delta = desired > limit ? limit : desired;
if (delta > ZERO) {
_custodied[account][token] = limit - delta;
}
}
}
/**
* get a account's token balance - the number of tokens that can be used
* to perform certain actions such as inking or heating
* @param account the account in question
* @param token the token balance being queried
*/
function balanceOf(
address account,
address token
) external view returns (uint256) {
return _custodied[account][token];
}
/**
* access the timeline, owner, duration, contribution count and seed if it exists
* the number of locations that recreates the final hash is equivalent the number of required seed contributions
* @param key the randomness key
*/
function randomness(
bytes32 key
) external view override returns (Randomness memory) {
unchecked {
return
Randomness({
owner: address(uint160(_timeline[key] >> NINE_SIX)), // 160 bits
usesTimestamp: (_timeline[key] >> EIGHT) & ONE == ONE, // 1 bit
callAtChange: _shouldCall(_timeline[key]),
start: uint256(uint48(_timeline[key] >> FOUR_EIGHT)), // 48 bits
duration: uint256(
uint256(uint48(_timeline[key])) >> (EIGHT + TWO)
), // only 38 bits
contributed: uint256(uint8(_timeline[key])), // 8 bits
timeline: _timeline[key],
seed: _seed[key]
});
}
}
/**
* get the latest key generated by a provided address. restrict to same transaction if desired
* @param owner the address that requested randomness
* @param onlySameTx whether or not to only consider randomness that has been requested within this transaction
* utilizes transient storage and provides certain guarantees regarding the relationship between randomness
* and the its utility to outsiders depending on how the transaction was executed
*/
function latest(
address owner,
bool onlySameTx
) external view override returns (bytes32 key) {
// key = _NAMESPACE.erc7201Slot().deriveMapping(owner).asBytes32().tload();
key = _latestInTx[owner][_txHash()];
if (key == bytes32(ZERO)) {
if (onlySameTx) {
revert Errors.UnableToService();
}
key = _latest[owner];
}
}
/**
* check if a preimage at the provided location has been consumed / accessed for randomness
* @param info preimage location info to locate the preimage
* @return consumed a boolean to indicate that the location has or has not been consumed
*/
function consumed(
PreimageLocation.Info calldata info
) external view override returns (bool) {
return _consumed({info: info, encodedToken: info.encodeToken()});
}
/**
* check for a minimum number of unconsumed preimages. provide a duration in
* seconds or blocks to consider the randomness set to be valid
* @param required the minimum number of locations required to be a valid (desired) set
* @param settings the settings required to setup the randomness campaign
* @notice if the duration in the settings is lower than any location or the
* duration is timestamp does not match, then the contract will err
* @param potentialLocations the locations to check for unconsumed preimages
* @dev note that the contract stores the latest key for each owner in transient storage
* this allows for many other chained games to use the same randomness seeds and have guarantees
* that no secrets have been exposed before the initiating transaction has been mined
*/
function heat(
uint256 required,
PreimageLocation.Info calldata settings,
PreimageLocation.Info[] calldata potentialLocations
) external payable override returns (bytes32) {
unchecked {
bytes32[] memory locations = new bytes32[](required);
address account = LibMulticaller.senderOrSigner();
{
if (msg.value > ZERO) {
_custodied[account][address(0)] += msg.value;
}
if (settings.provider == address(0)) {
revert Errors.UnableToService();
}
if (
required == ZERO ||
required > TWO_FIVE_FIVE ||
required > potentialLocations.length
) {
// only 255 len or fewer allowed
revert Errors.UnableToService();
}
if (
(uint256(uint40(settings.duration << TWO)) >> TWO) !=
settings.duration
) {
revert Errors.Misconfigured();
}
uint256 len = potentialLocations.length;
uint256 i;
uint256 contributing;
uint256 amount;
bytes32 section;
address token = potentialLocations[ZERO].token;
PreimageLocation.Info calldata target;
do {
target = potentialLocations[i];
// non zero means that the value exists
if (token != target.token) {
revert Errors.Misconfigured();
}
if (
target.durationIsTimestamp !=
settings.durationIsTimestamp
) {
revert Errors.Misconfigured();
}
// target.minDuration > duration
if (target.duration > settings.duration) {
revert Errors.Misconfigured();
}
section = target.section();
if (_ignite({info: target, section: section})) {
locations[contributing] = section.location(
target.index
);
amount += target.price;
++contributing;
if (required == contributing) {
break;
}
}
++i;
} while (i < len);
if (contributing < required) {
// let other contracts revert if they must
revert Errors.UnableToService();
}
if (
amount > ZERO &&
amount >
_decrementTokenAmount({
account: account,
token: token,
desired: amount
})
) {
revert Errors.MissingPayment();
}
}
{
bytes32 key = locations.hash();
// front load the cost of requesting randomness
// put it on the shoulders of the consumer
// this can probably be optimized
_timeline[key] = _timelineFromInputs({
owner: settings.provider,
callAtChange: settings.callAtChange,
// we already checked expiry offset above is constrained to 38 bits
expiryOffset: (settings.duration << ONE) |
(settings.durationIsTimestamp ? ONE : ZERO),
start: settings.durationIsTimestamp
? block.timestamp
: block.number
});
_storeLatest({provider: settings.provider, key: key});
emit Start({owner: settings.provider, key: key});
return key;
}
}
}
function _storeLatest(address provider, bytes32 key) internal {
// _NAMESPACE
// .erc7201Slot()
// .deriveMapping(settings.provider)
// .asBytes32()
// .store(key);
// this mode is insufficient for randomness due to block builders
// being able to name their own transaction order
_latestInTx[provider][_txHash()] = key;
_latest[provider] = key;
}
function _txHash() internal view returns (bytes32) {
return
keccak256(
abi.encodePacked(
block.coinbase,
block.basefee,
block.chainid,
block.timestamp,
block.number,
blockhash(block.number),
tx.origin,
tx.gasprice
)
);
}
/**
* encodes a timeline that will only change the last 8 bits as secrets are revealed
* @param owner the owner of the randomness - the address that will be refunded if not all secrets are provided in a timely manner
* @param expiryOffset the expiration offset from the time that the randomness was first requested
* @param start the start time or block number
* @return timeline an encoded number with relevant owner, and timing data
*/
function _timelineFromInputs(
address owner,
bool callAtChange,
uint256 expiryOffset,
uint256 start
) internal pure returns (uint256) {
return
(uint256(uint160(owner)) << NINE_SIX) |
(uint256((uint48(start))) << FOUR_EIGHT) |
(uint256(uint40(expiryOffset << TWO)) << (EIGHT - ONE)) |
(uint256(callAtChange ? ONE : ZERO) << EIGHT); // last 8 bits left blank for counting as secrets are revealed
}
/**
* retrieve the pointer or address that holds the series of preimages for a tranche of secrets
* @param info access the pointer as defined by the preimage location
* @return pointer the address that holds preimages
*/
function pointer(
PreimageLocation.Info calldata info
) external view override returns (address) {
return
_pointers[info.provider][info.encodeToken()][info.price][
info.offset
];
}
/**
* advertise immutable randomness preimages for future revelation. imagine painting a die with invisible ink
* @param data the concatenated, immutable preimages to write on chain
* @dev if data length is > (24576-32), then this method will fail
* @dev if data is not evenly divisible by 32, then this method will fail
* @dev it is best to call this infrequently but to do so with as
* much calldata as possible to increase gas savings for randomness providers
*/
function ink(
PreimageLocation.Info memory info,
bytes calldata data
) external payable {
unchecked {
uint256 count = data.length / THREE_TWO;
if (data.length == ZERO || data.length % THREE_TWO != ZERO) {
revert Errors.Misconfigured();
}
// access control regulated by the sender/signer
address account = LibMulticaller.senderOrSigner();
if (msg.value > ZERO) {
_custodied[account][address(0)] += msg.value;
}
uint256 limit = _custodied[account][info.token];
uint256 toStake = count * info.price;
if (limit < toStake) {
revert Errors.MissingPayment();
}
// at this point, the only address that can unlock this value
// is one that has access to the secrets or pays for randomness and does not get it in a timely manner
_custodied[account][info.token] -= toStake;
// owner of the newly created randomness set by calldata
address owner = info.provider;
if (owner == address(0)) {
revert Errors.UnableToService();
}
uint256 tkn = info.encodeToken();
uint256 start = _preimageCount[owner][tkn][info.price];
address pntr = data.write(); // deploy a contract with immutable preimages written into it
_pointers[owner][tkn][info.price][start] = pntr;
_preimageCount[owner][tkn][info.price] = start + count;
info.provider = owner;
info.offset = start;
emit Ink({
sender: account,
provider: owner,
section: info.section(),
offset: (start << ONE_TWO_EIGHT) | (start + count),
pointer: pntr
});
}
}
/**
* refund an owner of randomness for any secrets that are not written on chain.
* the amount refunded is equal to the amount of tokens for each preimage that was not revealed.
* @dev this method will fail if the timeline has not yet expired. because the duration must be >= to the
* location defined by each provider, implicit consent and declaration has been provided by each provider
* that they will have their randomness on chain by the time this method can be executed
* @param key the key of the randomness that did not have all of its secrets revealed
* @param info the set of locations of the randomness that was requested
*/
function chop(
bytes32 key,
PreimageLocation.Info[] calldata info
) external payable {
unchecked {
if (msg.value > ZERO) {
_custodied[LibMulticaller.senderOrSigner()][address(0)] += msg
.value;
}
if (_seed[key] != bytes32(ZERO)) {
// don't penalize, because a provider could slip in before
return;
}
uint256 timeline = _timeline[key];
if (!_expired({timeline: timeline})) {
revert Errors.UnableToService();
}
if (_chopped[key]) {
revert Errors.UnableToService();
}
uint256 remaining;
uint256 original;
uint256 i;
uint256 len = info.length;
bytes32[] memory locations = new bytes32[](len);
bytes32 revealedSecret;
do {
(, revealedSecret) = _secret({
info: info[i],
encodedToken: info[i].encodeToken()
});
if (revealedSecret == bytes32(ZERO)) {
// take the provider's stake
remaining += info[i].price;
}
original += info[i].price;
locations[i] = info[i].location();
++i;
} while (i < len);
if (locations.hash() != key) {
revert Errors.NotInCohort();
}
// for any secrets that do not reach the chain, the payment
// AND the staked amount is released to the owner
_chopped[key] = true;
_reverseCharges({
timeline: timeline,
key: key,
token: info[ZERO].token,
payout: remaining + original
});
if (_shouldCall(timeline)) {
address(uint160(timeline >> NINE_SIX)).call(
abi.encodeWithSelector(
ConsumerReceiver.onChop.selector,
key
)
);
}
emit Chop({key: key});
}
}
/**
* write a randomness request's secrets into storage
* @param key the randomness key. provided info param must be hashed to recreate this key
* @param info the raw location info of preimages
* @param revealed the list of secrets that must match the written preimages
*/
function cast(
bytes32 key,
PreimageLocation.Info[] calldata info,
bytes32[] memory revealed
) external payable returns (CastState) {
unchecked {
if (msg.value > ZERO) {
_custodied[LibMulticaller.senderOrSigner()][address(0)] += msg
.value;
}
bytes32 seed = _seed[key];
if (seed != bytes32(ZERO)) {
return CastState.SEED_SET;
}
if (_chopped[key]) {
revert Errors.UnableToService();
}
uint256 len = info.length;
uint256 i;
uint256 total;
uint256 timeline = _timeline[key];
{
bytes32[] memory locations = new bytes32[](len);
uint256 firstFlicks;
bool first;
bool missing;
bytes32 linkedSecret;
do {
if (revealed[i] != bytes32(ZERO)) {
(locations[i], first) = _flick({
info: info[i],
formerSecret: revealed[i]
});
if (first) {
++firstFlicks;
}
} else {
(revealed[i], linkedSecret) = _secret({
info: info[i],
encodedToken: info[i].encodeToken()
});
if (revealed[i] == bytes32(ZERO)) {
if (linkedSecret == bytes32(ZERO)) {
missing = true;
locations[i] = info[i].location();
} else {
(locations[i], first) = _flick({
info: info[i],
formerSecret: linkedSecret
});
revealed[i] = linkedSecret;
if (first) {
++firstFlicks;
}
}
} else {
locations[i] = info[i].location();
}
}
total += info[i].price;
++i;
} while (i < len);
if (key != locations.hash()) {
revert Errors.NotInCohort();
}
// this allows users to submit partial secret sets and unlock their staked tokens
// without risking omission attacks from late or downed actors
timeline += firstFlicks;
_timeline[key] = timeline;
if (missing) {
return CastState.MISSING_SECRET;
}
// mark as generated
seed = revealed.hash();
_seed[key] = seed;
emit Cast({key: key, seed: seed});
}
{
// until the seed is properly formed, no one validator
// knows which one of them is going to get the bonus
// only the last validator to reveal their secret has an edge in that they can choose to
// omit their secret, they will however, forfeit their staked tokens to whoever calls chop
PreimageLocation.Info calldata item = info[
_random({key: seed, upper: len})
];
if (_expired({timeline: timeline})) {
// if secrets are submitted late, then the owner gets half of their payment back
uint256 payout = total / 2;
total -= payout;
_reverseCharges({
timeline: timeline,
key: key,
token: item.token,
payout: payout
});
// can be used as reputation
emit Expired({key: key});
}
_custodied[item.provider][item.token] += total;
}
if (_shouldCall(timeline)) {
address(uint160(timeline >> NINE_SIX)).call(
abi.encodeWithSelector(
ConsumerReceiver.onCast.selector,
key,
seed
)
);
}
return CastState.SCATTERED;
}
}
/**
* check a timeline's boolean flags in order to determine if a method should be called for it
* @param timeline provides the context to check
*/
function _shouldCall(uint256 timeline) internal pure returns (bool) {
return (timeline << 247) >> TWO_FIVE_FIVE == ONE;
}
/**
* retrieve a random number between 0 and the upper limit (exclusive)
* @param key the randomness key
* @param upper the upper limit of the uniform range
*/
function _random(
bytes32 key,
uint256 upper
) internal view returns (uint256) {
return LibPRNG.PRNG({state: uint256(_seed[key])}).uniform(upper);
}
/**
* hand off tokens between an address (caller) or optional recipient and this contract
* @param recipient the recipient of tokens - either the account in this contract or an address outside of this contract
* @param token the token address to transfer - use zero address for native tokens
* @param amount a number of tokens to transfer
*/
function handoff(
address recipient,
address token,
int256 amount
) external payable {
unchecked {
address account = LibMulticaller.senderOrSigner();
recipient = recipient == address(0) ? account : recipient;
if (amount < 0) {
// move take tokens from signer to recipient custodied by signer
_custodied[recipient][token] += _receiveTokens(
account,
token,
uint256(-amount)
);
} else {
// move tokens from signer to recipient custodied by contract
_distribute(
recipient,
token,
_decrementTokenAmount(account, token, uint256(amount))
);
}
}
}
/**
* when a provider no longer has access to appropriate data, he should
* invalidate the data that he has written so that he does not confuse front ends
* @param info the unhashed section to bleach
* @dev calling this method means that all preimages will be invalidated. it will be costly
*/
function bleach(PreimageLocation.Info memory info) external payable {
unchecked {
address provider = LibMulticaller.senderOrSigner();
if (msg.value > ZERO) {
_custodied[provider][address(0)] += msg.value;
}
if (provider != info.provider) {
revert Errors.SignerMismatch();
}
uint256 encodedToken = info.encodeToken();
uint256 size = _pointerSize({
info: info,
encodedToken: encodedToken
}) / THREE_TWO;
bytes32 section = info.section();
// consumes a whole pointer
uint256 amount;
uint256 start = info.offset;
uint256 end = start + size; // exclusive end
uint256 mask;
uint256 len;
uint256 f;
uint256 i;
LibBitmap.Bitmap storage bitmap = _accessFlags[info.provider][
encodedToken
][info.price];
uint256 flags;
uint256 targetedFlags;
uint256 max = type(uint256).max;
do {
if (len == ZERO) {
len = TWO_FIVE_SIX - (start % TWO_FIVE_SIX);
}
if (start + len > end) {
len = end - start;
}
mask = (max >> (TWO_FIVE_SIX - len)); // at root (all f's to the right)
flags = bitmap.map[start / TWO_FIVE_SIX];
targetedFlags = ((flags << (TWO_FIVE_SIX - (start + len))) >>
(TWO_FIVE_SIX - len)); // at root (all bits to the right)
if (targetedFlags < mask) {
bitmap.setBatch(start, len);
i = start % TWO_FIVE_SIX;
f = i + len;
do {
if (((flags >> i) & ONE) == ZERO) {
amount += info.price;
}
++i;
} while (i < f);
}
start += len;
len = TWO_FIVE_SIX;
} while (start < end);
if (amount > ZERO) {
emit Bleach({provider: provider, section: section});
// assume that amount is > 0 otherwise there is not economic reason to run this fn
// therefore writing the sstore is always going to have a non zero delta
_custodied[provider][info.token] += amount;
}
}
}
}
contracts/SlotDerivation.sol
// SPDX-License-Identifier: MIT
// This file was procedurally generated from scripts/generate/templates/SlotDerivation.js.
pragma solidity ^0.8.24;
/**
* @dev Library for computing storage (and transient storage) locations from namespaces and deriving slots
* corresponding to standard patterns. The derivation method for array and mapping matches the storage layout used by
* the solidity language / compiler.
*
* See https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays[Solidity docs for mappings and dynamic arrays.].
*
* Example usage:
* ```solidity
* contract Example {
* // Add the library methods
* using StorageSlot for bytes32;
* using SlotDerivation for bytes32;
*
* // Declare a namespace
* string private constant _NAMESPACE = "<namespace>" // eg. OpenZeppelin.Slot
*
* function setValueInNamespace(uint256 key, address newValue) internal {
* _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value = newValue;
* }
*
* function getValueInNamespace(uint256 key) internal view returns (address) {
* return _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value;
* }
* }
* ```
*
* TIP: Consider using this library along with {StorageSlot}.
*
* NOTE: This library provides a way to manipulate storage locations in a non-standard way. Tooling for checking
* upgrade safety will ignore the slots accessed through this library.
*/
library SlotDerivation {
/**
* @dev Derive an ERC-7201 slot from a string (namespace).
*/
function erc7201Slot(string memory namespace) internal pure returns (bytes32 slot) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, sub(keccak256(add(namespace, 0x20), mload(namespace)), 1))
slot := and(keccak256(0x00, 0x20), not(0xff))
}
}
// /**
// * @dev Add an offset to a slot to get the n-th element of a structure or an array.
// */
// function offset(bytes32 slot, uint256 pos) internal pure returns (bytes32 result) {
// unchecked {
// return bytes32(uint256(slot) + pos);
// }
// }
// /**
// * @dev Derive the location of the first element in an array from the slot where the length is stored.
// */
// function deriveArray(bytes32 slot) internal pure returns (bytes32 result) {
// /// @solidity memory-safe-assembly
// assembly {
// mstore(0x00, slot)
// result := keccak256(0x00, 0x20)
// }
// }
/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, address key) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, key)
mstore(0x20, slot)
result := keccak256(0x00, 0x40)
}
}
// /**
// * @dev Derive the location of a mapping element from the key.
// */
// function deriveMapping(bytes32 slot, bool key) internal pure returns (bytes32 result) {
// /// @solidity memory-safe-assembly
// assembly {
// mstore(0x00, key)
// mstore(0x20, slot)
// result := keccak256(0x00, 0x40)
// }
// }
// /**
// * @dev Derive the location of a mapping element from the key.
// */
// function deriveMapping(bytes32 slot, bytes32 key) internal pure returns (bytes32 result) {
// /// @solidity memory-safe-assembly
// assembly {
// mstore(0x00, key)
// mstore(0x20, slot)
// result := keccak256(0x00, 0x40)
// }
// }
// /**
// * @dev Derive the location of a mapping element from the key.
// */
// function deriveMapping(bytes32 slot, uint256 key) internal pure returns (bytes32 result) {
// /// @solidity memory-safe-assembly
// assembly {
// mstore(0x00, key)
// mstore(0x20, slot)
// result := keccak256(0x00, 0x40)
// }
// }
// /**
// * @dev Derive the location of a mapping element from the key.
// */
// function deriveMapping(bytes32 slot, int256 key) internal pure returns (bytes32 result) {
// /// @solidity memory-safe-assembly
// assembly {
// mstore(0x00, key)
// mstore(0x20, slot)
// result := keccak256(0x00, 0x40)
// }
// }
// /**
// * @dev Derive the location of a mapping element from the key.
// */
// function deriveMapping(bytes32 slot, string memory key) internal pure returns (bytes32 result) {
// /// @solidity memory-safe-assembly
// assembly {
// let length := mload(key)
// let begin := add(key, 0x20)
// let end := add(begin, length)
// let cache := mload(end)
// mstore(end, slot)
// result := keccak256(begin, add(length, 0x20))
// mstore(end, cache)
// }
// }
// /**
// * @dev Derive the location of a mapping element from the key.
// */
// function deriveMapping(bytes32 slot, bytes memory key) internal pure returns (bytes32 result) {
// /// @solidity memory-safe-assembly
// assembly {
// let length := mload(key)
// let begin := add(key, 0x20)
// let end := add(begin, length)
// let cache := mload(end)
// mstore(end, slot)
// result := keccak256(begin, add(length, 0x20))
// mstore(end, cache)
// }
// }
}
contracts/StorageSlot.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
pragma solidity ^0.8.24;
library StorageSlot {
struct Bytes32Slot {
bytes32 value;
}
/**
* @dev UDVT that represent a slot holding a bytes32.
*/
type Bytes32SlotType is bytes32;
/**
* @dev Cast an arbitrary slot to a Bytes32SlotType.
*/
function asBytes32(bytes32 slot) internal pure returns (Bytes32SlotType) {
return Bytes32SlotType.wrap(slot);
}
/**
* @dev Load the value held at location `slot` in transient storage.
*/
function tload(Bytes32SlotType slot) internal view returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
value := tload(slot)
}
}
/**
* @dev Store `value` at location `slot` in transient storage.
*/
function tstore(Bytes32SlotType slot, bytes32 value) internal {
/// @solidity memory-safe-assembly
assembly {
tstore(slot, value)
}
}
}
contracts/hardhat-dependency-compiler/multicaller/src/MulticallerEtcher.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity >0.0.0;
import 'multicaller/src/MulticallerEtcher.sol';
contracts/hardhat-dependency-compiler/multicaller/src/MulticallerWithSender.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity >0.0.0;
import 'multicaller/src/MulticallerWithSender.sol';
contracts/hardhat-dependency-compiler/multicaller/src/MulticallerWithSigner.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity >0.0.0;
import 'multicaller/src/MulticallerWithSigner.sol';
contracts/implementations/ConsumerReceiver.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;
abstract contract ConsumerReceiver {
function onReverse(
bytes32 /*key*/,
address /*token*/,
uint256 /*amount*/
) external virtual;
function onCast(bytes32 /*key*/, bytes32 /*seed*/) external virtual;
function onChop(bytes32 /*key*/) external virtual;
}
contracts/implementations/IERC20.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;
abstract contract IERC20 {
function transfer(address to, uint256 amount) external virtual returns (bool);
}
contracts/implementations/IRandom.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;
import {PreimageLocation} from "../PreimageLocation.sol";
abstract contract IRandom {
uint256 internal constant ZERO = 0;
uint256 internal constant ONE = 1;
uint256 internal constant TWO = 2;
uint256 internal constant EIGHT = 8;
uint256 internal constant ONE_SIX = 16;
uint256 internal constant THREE_TWO = 32;
uint256 internal constant FOUR_EIGHT = 48;
uint256 internal constant NINE_SIX = 96;
uint256 internal constant ONE_TWO_EIGHT = 128;
uint256 internal constant ONE_SIX_ZERO = 160;
uint256 internal constant TWO_ZERO_EIGHT = 208;
uint256 internal constant TWO_ZERO_NINE = ONE + TWO_ZERO_EIGHT;
uint256 internal constant TWO_FOUR_EIGHT = 248;
uint256 internal constant TWO_FIVE_FIVE = 255;
uint256 internal constant TWO_FIVE_SIX = 256;
mapping(bytes32 key => uint256 timeline) internal _timeline;
mapping(bytes32 key => bytes32 seed) internal _seed;
struct Randomness {
address owner;
bool callAtChange;
bool usesTimestamp;
uint256 duration;
uint256 start;
uint256 timeline;
uint256 contributed;
bytes32 seed;
}
function heat(
uint256 required,
PreimageLocation.Info calldata settings,
PreimageLocation.Info[] calldata info
) external payable virtual returns (bytes32);
function pointer(
PreimageLocation.Info calldata info
) external view virtual returns (address);
function consumed(
PreimageLocation.Info calldata info
) external view virtual returns (bool);
function randomness(
bytes32 key
) external view virtual returns (Randomness memory);
function latest(
address account,
bool onlySameTx
) external view virtual returns (bytes32);
function expired(uint256 timeline) external view virtual returns (bool) {
return _expired(timeline);
}
function _expired(uint256 timeline) internal view virtual returns (bool) {
unchecked {
// end
return
(
(timeline << (TWO_FIVE_FIVE - (EIGHT + ONE))) >>
TWO_FIVE_FIVE ==
ZERO
? block.number
: block.timestamp
) -
// start
(uint256(uint48(timeline >> FOUR_EIGHT))) >
// expiration delta
(uint256(uint40(timeline) >> (EIGHT + TWO)));
}
}
}
contracts/test/ConsumerEmitter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ConsumerIncomplete} from "./ConsumerIncomplete.sol";
import {PreimageLocation} from "../PreimageLocation.sol";
import {IRandom} from "../implementations/IRandom.sol";
import {ConsumerReceiver} from "../implementations/ConsumerReceiver.sol";
event Reverse(bytes32 key, address token, uint256 amount);
event Chop(bytes32 key);
event Cast(bytes32 key, bytes32 seed);
contract ConsumerEmitter is ConsumerIncomplete, ConsumerReceiver {
constructor(address _rand) payable ConsumerIncomplete(_rand) {}
function onCast(bytes32 key, bytes32 seed) external override {
emit Cast({
key: key,
seed: seed
});
}
function onChop(bytes32 key) external override {
emit Chop({
key: key
});
}
function onReverse(
bytes32 key,
address token,
uint256 amount
) external override {
emit Reverse({
key: key,
token: token,
amount: amount
});
}
}
contracts/test/ConsumerIncomplete.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {PreimageLocation} from "../PreimageLocation.sol";
import {IRandom} from "../implementations/IRandom.sol";
contract ConsumerIncomplete {
address immutable rand;
constructor(address _rand) payable {
rand = _rand;
}
function heat(
uint256 required,
PreimageLocation.Info calldata settings,
PreimageLocation.Info[] calldata potentialLocations
) external {
IRandom(rand).heat(
required,
PreimageLocation.Info({
provider: settings.provider,
callAtChange: settings.callAtChange,
duration: settings.duration,
durationIsTimestamp: settings.durationIsTimestamp,
token: settings.token,
price: settings.price,
offset: settings.offset,
index: settings.index
}),
potentialLocations
);
}
receive() external payable {}
}
contracts/test/ERC20.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;
import {ERC20 as SolERC20} from "solady/src/tokens/ERC20.sol";
contract ERC20 is SolERC20 {
bool internal immutable _shouldBurn;
uint256 internal constant ONE_ETHER = 1 ether;
uint256 internal constant TAX_NUMERATOR = ONE_ETHER - (ONE_ETHER / 100);
constructor(bool shouldBurn) payable {
_shouldBurn = shouldBurn;
}
function name() public pure override returns (string memory) {
return "";
}
function symbol() public pure override returns (string memory) {
return "";
}
function mint(address recipient, uint256 amount) external {
_mint(recipient, amount);
}
function _afterTokenTransfer(address from, address to, uint256 amount) internal override {
if (_shouldBurn && from != address(0) && to != address(0)) {
_burn(to, amount - ((amount * TAX_NUMERATOR) / ONE_ETHER));
}
}
function taxRatio() external pure returns (uint256) {
return TAX_NUMERATOR;
}
}
contracts/test/Etcher.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {MulticallerEtcher} from "multicaller/src/MulticallerEtcher.sol";
contract Etcher {
function multicallerWithSender() external {
MulticallerEtcher.multicallerWithSender();
}
}
multicaller/src/LibMulticaller.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/**
* @title LibMulticaller
* @author vectorized.eth
* @notice Library to read the `msg.sender` of the multicaller with sender contract.
*
* @dev Note:
* The functions in this library do NOT guard against reentrancy.
* A single transaction can recurse through different Multicallers
* (e.g. `MulticallerWithSender -> contract -> MulticallerWithSigner -> contract`).
*
* Think of these functions like `msg.sender`.
*
* If your contract `C` can handle reentrancy safely with plain old `msg.sender`
* for any `A -> C -> B -> C`, you should be fine substituting `msg.sender` with these functions.
*/
library LibMulticaller {
/**
* @dev The address of the multicaller contract.
*/
address internal constant MULTICALLER = 0x0000000000002Bdbf1Bf3279983603Ec279CC6dF;
/**
* @dev The address of the multicaller with sender contract.
*/
address internal constant MULTICALLER_WITH_SENDER = 0x00000000002Fd5Aeb385D324B580FCa7c83823A0;
/**
* @dev The address of the multicaller with signer contract.
*/
address internal constant MULTICALLER_WITH_SIGNER = 0x000000000000D9ECebf3C23529de49815Dac1c4c;
/**
* @dev Returns the caller of `aggregateWithSender` on `MULTICALLER_WITH_SENDER`.
*/
function multicallerSender() internal view returns (address result) {
return at(MULTICALLER_WITH_SENDER);
}
/**
* @dev Returns the signer of `aggregateWithSigner` on `MULTICALLER_WITH_SIGNER`.
*/
function multicallerSigner() internal view returns (address result) {
return at(MULTICALLER_WITH_SIGNER);
}
/**
* @dev Returns the caller of `aggregateWithSender` on `MULTICALLER_WITH_SENDER`,
* if the current context's `msg.sender` is `MULTICALLER_WITH_SENDER`.
* Otherwise, returns `msg.sender`.
*/
function sender() internal view returns (address result) {
return resolve(MULTICALLER_WITH_SENDER);
}
/**
* @dev Returns the caller of `aggregateWithSigner` on `MULTICALLER_WITH_SIGNER`,
* if the current context's `msg.sender` is `MULTICALLER_WITH_SIGNER`.
* Otherwise, returns `msg.sender`.
*/
function signer() internal view returns (address) {
return resolve(MULTICALLER_WITH_SIGNER);
}
/**
* @dev Returns the caller or signer at `a`.
* @param a The multicaller with sender / signer.
*/
function at(address a) internal view returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x00)
if iszero(staticcall(gas(), a, codesize(), 0x00, 0x00, 0x20)) {
revert(codesize(), codesize()) // For better gas estimation.
}
result := mload(0x00)
}
}
/**
* @dev Returns the caller or signer at `a`, if the caller is `a`.
* @param a The multicaller with sender / signer.
*/
function resolve(address a) internal view returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, caller())
if eq(caller(), a) {
if iszero(staticcall(gas(), a, codesize(), 0x00, 0x00, 0x20)) {
revert(codesize(), codesize()) // For better gas estimation.
}
}
result := mload(0x00)
}
}
/**
* @dev Returns the caller of `aggregateWithSender` on `MULTICALLER_WITH_SENDER`,
* if the current context's `msg.sender` is `MULTICALLER_WITH_SENDER`.
* Returns the signer of `aggregateWithSigner` on `MULTICALLER_WITH_SIGNER`,
* if the current context's `msg.sender` is `MULTICALLER_WITH_SIGNER`.
* Otherwise, returns `msg.sender`.
*/
function senderOrSigner() internal view returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, caller())
let withSender := MULTICALLER_WITH_SENDER
if eq(caller(), withSender) {
if iszero(staticcall(gas(), withSender, codesize(), 0x00, 0x00, 0x20)) {
revert(codesize(), codesize()) // For better gas estimation.
}
}
let withSigner := MULTICALLER_WITH_SIGNER
if eq(caller(), withSigner) {
if iszero(staticcall(gas(), withSigner, codesize(), 0x00, 0x00, 0x20)) {
revert(codesize(), codesize()) // For better gas estimation.
}
}
result := mload(0x00)
}
}
}
multicaller/src/Multicaller.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/**
* @title Multicaller
* @author vectorized.eth
* @notice Contract that allows for efficient aggregation
* of multiple calls in a single transaction.
*/
contract Multicaller {
// =============================================================
// ERRORS
// =============================================================
/**
* @dev The lengths of the input arrays are not the same.
*/
error ArrayLengthsMismatch();
// =============================================================
// AGGREGATION OPERATIONS
// =============================================================
/**
* @dev Aggregates multiple calls in a single transaction.
* @param targets An array of addresses to call.
* @param data An array of calldata to forward to the targets.
* @param values How much ETH to forward to each target.
* @param refundTo The address to transfer any remaining ETH in the contract after the calls.
* If `address(0)`, remaining ETH will NOT be refunded.
* If `address(1)`, remaining ETH will be refunded to `msg.sender`.
* If anything else, remaining ETH will be refunded to `refundTo`.
* @return An array of the returndata from each call.
*/
function aggregate(
address[] calldata targets,
bytes[] calldata data,
uint256[] calldata values,
address refundTo
) external payable returns (bytes[] memory) {
assembly {
if iszero(and(eq(targets.length, data.length), eq(data.length, values.length))) {
// Store the function selector of `ArrayLengthsMismatch()`.
mstore(returndatasize(), 0x3b800a46)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
let resultsSize := 0x40
if data.length {
let results := 0x40
// Left shift by 5 is equivalent to multiplying by 0x20.
data.length := shl(5, data.length)
// Copy the offsets from calldata into memory.
calldatacopy(results, data.offset, data.length)
// Offset into `results`.
let resultsOffset := data.length
// Pointer to the end of `results`.
let end := add(results, data.length)
// For deriving the calldata offsets from the `results` pointer.
let valuesOffsetDiff := sub(values.offset, results)
let targetsOffsetDiff := sub(targets.offset, results)
for {} 1 {} {
// The offset of the current bytes in the calldata.
let o := add(data.offset, mload(results))
let memPtr := add(resultsOffset, 0x40)
// Copy the current bytes from calldata to the memory.
calldatacopy(
memPtr,
add(o, 0x20), // The offset of the current bytes' bytes.
calldataload(o) // The length of the current bytes.
)
if iszero(
call(
gas(), // Remaining gas.
calldataload(add(targetsOffsetDiff, results)), // Address to call.
calldataload(add(valuesOffsetDiff, results)), // ETH to send.
memPtr, // Start of input calldata in memory.
calldataload(o), // Size of input calldata.
0x00, // We will use returndatacopy instead.
0x00 // We will use returndatacopy instead.
)
) {
// Bubble up the revert if the call reverts.
returndatacopy(0x00, 0x00, returndatasize())
revert(0x00, returndatasize())
}
// Append the current `resultsOffset` into `results`.
mstore(results, resultsOffset)
// Append the returndatasize, and the returndata.
mstore(memPtr, returndatasize())
returndatacopy(add(memPtr, 0x20), 0x00, returndatasize())
// Advance the `resultsOffset` by `returndatasize() + 0x20`,
// rounded up to the next multiple of 0x20.
resultsOffset := and(add(add(resultsOffset, returndatasize()), 0x3f), not(0x1f))
// Advance the `results` pointer.
results := add(results, 0x20)
if eq(results, end) { break }
}
resultsSize := add(resultsOffset, 0x40)
}
if refundTo {
// Force transfers all the remaining ETH in the contract to `refundTo`,
// with a gas stipend of 100000, which should be enough for most use cases.
// If sending via a regular call fails, force sends the ETH by
// creating a temporary contract which uses `SELFDESTRUCT` to force send the ETH.
if selfbalance() {
// If `refundTo` is `address(1)`, replace it with the `msg.sender`.
refundTo := xor(refundTo, mul(eq(refundTo, 1), xor(refundTo, caller())))
// Transfer the ETH and check if it succeeded or not.
if iszero(
call(100000, refundTo, selfbalance(), codesize(), 0x00, codesize(), 0x00)
) {
mstore(0x00, refundTo) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
// We can directly use `SELFDESTRUCT` in the contract creation.
// Compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758
if iszero(create(selfbalance(), 0x0b, 0x16)) {
// Coerce gas estimation to provide enough gas for the `create` above.
revert(codesize(), codesize())
}
}
}
}
mstore(0x00, 0x20) // Store the memory offset of the `results`.
mstore(0x20, targets.length) // Store `targets.length` into `results`.
// Direct return.
return(0x00, resultsSize)
}
}
/**
* @dev For receiving ETH.
* Does nothing and returns nothing.
* Called instead of `fallback()` if the calldatasize is zero.
*/
receive() external payable {}
/**
* @dev Decompresses the calldata and performs a delegatecall
* with the decompressed calldata to itself.
*
* Accompanying JavaScript library to compress the calldata:
* https://github.com/vectorized/solady/blob/main/js/solady.js
* (See: `LibZip.cdCompress`)
*/
fallback() external payable {
assembly {
// If the calldata starts with the bitwise negation of
// `bytes4(keccak256("aggregate(address[],bytes[],uint256[],address)"))`.
let s := calldataload(returndatasize())
if eq(shr(224, s), 0x66e0daa0) {
mstore(returndatasize(), not(s))
let o := 4
for { let i := o } lt(i, calldatasize()) {} {
let c := byte(returndatasize(), calldataload(i))
i := add(i, 1)
if iszero(c) {
let d := byte(returndatasize(), calldataload(i))
i := add(i, 1)
// Fill with either 0xff or 0x00.
mstore(o, not(returndatasize()))
if iszero(gt(d, 0x7f)) { codecopy(o, codesize(), add(d, 1)) }
o := add(o, add(and(d, 0x7f), 1))
continue
}
mstore8(o, c)
o := add(o, 1)
}
let success := delegatecall(gas(), address(), 0x00, o, 0x00, 0x00)
returndatacopy(0x00, 0x00, returndatasize())
if iszero(success) { revert(0x00, returndatasize()) }
return(0x00, returndatasize())
}
revert(returndatasize(), returndatasize())
}
}
}
multicaller/src/MulticallerEtcher.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "./Multicaller.sol";
import "./MulticallerWithSender.sol";
import "./MulticallerWithSigner.sol";
import "./LibMulticaller.sol";
/**
* @title LibMulticaller
* @author vectorized.eth
* @notice Library for etching the multicaller contracts for testing in Foundry forge.
* It uses the VM cheatcodes to etch the multicaller contracts and initialize their storage.
* Not to be used on an actual EVM chain.
*/
library MulticallerEtcher {
// =============================================================
// CONSTANTS
// =============================================================
/**
* @dev The initcode for the multicaller.
*/
bytes internal constant MULTICALLER_INITCODE =
hex"60808060405234610016576102b9908161001c8239f35b600080fdfe60806040526004361015610015575b366101fd57005b6000803560e01c63991f255f1461002c575061000e565b60803660031901126100aa5767ffffffffffffffff6004358181116100b5576100599036906004016100b9565b916024358181116100b1576100729036906004016100b9565b916044359081116100ad5761008b9036906004016100b9565b6064359690959194906001600160a01b03881688036100aa57506100ef565b80fd5b8580fd5b8480fd5b8280fd5b9181601f840112156100ea5782359167ffffffffffffffff83116100ea576020808501948460051b0101116100ea57565b600080fd5b959390949295606093871487871416156101f0578693604097610166575b505050505080610125575b5060206000526020526000f35b47156101185733811860018214021860003881804785620186a0f1610118576000526073600b5360ff6020536016600b47f0156101625738610118565b3838fd5b8794919395979160051b9384878737848601945b835188019087810182359081602080950182376000808093838a8c603f19918291010135908c8b0101355af1156101e7578287523d90523d908583013e603f601f19913d010116930196898689146101d45750969261017a565b975050505091505001923880808061010d565b503d81803e3d90fd5b633b800a463d526004601cfd5b3d356366e0daa08160e01c14610211573d3dfd5b193d5260043d815b36811061023a57600080808581305af43d82803e15610236573d90f35b3d90fd5b8035821a92600180920193801561025757815301905b9091610219565b503d19815283820193607f90353d1a81811115610278575b16010190610250565b83810138843961026f56fea26469706673582212200dfa3a85cbd068a99fd4d5051615c4bde5995f9e1dd4a095bb55fc5af681c44064736f6c63430008120033";
/**
* @dev The salt for the multicaller to be deployed via
* 0age's immutable create2 factory.
*/
bytes32 internal constant MULTICALLER_CREATE2_SALT =
0x0000000000000000000000000000000000000000ef4834b251a91000a916248a;
/**
* @dev The initcode for the multicaller with sender.
*/
bytes internal constant MULTICALLER_WITH_SENDER_INITCODE =
hex"60806040819052600160a01b3d55610247908161001a8239f3fe60406080815260049081361015610023575b5050361561001e57600080fd5b6101f2565b600091823560e01c63d985f1e81461003b5750610011565b606090817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b85767ffffffffffffffff81358181116101b45761008690369084016101bc565b916024358181116101b05761009e90369086016101bc565b9590936044359283116101ac576100b98793369088016101bc565b9390938114911416156101a0577401000000000000000000000000000000000000000094853d5416156101955750602090813d52868252861561019157333d55929560051b93919287929185838537858901955b84518401988b80848d85019c8d81359283920190378c8a3585355af115610188577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe091838080603f9401990197828152019a3d90523d8d8683013e3d010116928689101561017f57929793949761010d565b878b55838a018bf35b8b3d81803e3d90fd5b873df35b63ab143c063d52601cfd5b84633b800a463d52601cfd5b8880fd5b8780fd5b8580fd5b8380fd5b9181601f840112156101ed5782359167ffffffffffffffff83116101ed576020808501948460051b0101116101ed57565b600080fd5b3d5473ffffffffffffffffffffffffffffffffffffffff163d5260203df3fea2646970667358221220802fc1f04a279628c77438e5942439f44c7eaf734a7dca754fef889a35be139764736f6c63430008120033";
/**
* @dev The salt for the multicaller with sender to be deployed via
* 0age's immutable create2 factory.
*/
bytes32 internal constant MULTICALLER_WITH_SENDER_CREATE2_SALT =
0x00000000000000000000000000000000000000006bfa48b413e5be01a8e9fe0c;
/**
* @dev The initcode for the multicaller with signer.
*/
bytes internal constant MULTICALLER_WITH_SIGNER_INITCODE =
hex"60808060405260013d55610b6d90816100168239f3fe6040608081526004361015610020575b50361561001b57600080fd5b610839565b6000803560e01c91826317447cf1146100aa57505080632eb48a80146100a55780633aeb2206146100a057806356b1a87f1461009b57806384b0196e1461009657806387ec11ca14610091578063ad3aacb81461008c5763f0c60f1a14610087573861000f565b6107e3565b610753565b610507565b6104b2565b61028f565b61023e565b6101a3565b346101275780600319360112610127576100c261012b565b9060243567ffffffffffffffff8111610123576100e3903690600401610172565b909284528060051b92845b848103610102575050602084526020520190f35b80602091830135808352603f8820549060ff161c60011681860152016100ee565b8380fd5b5080fd5b600435906001600160a01b038216820361014157565b600080fd5b602435906001600160a01b038216820361014157565b608435906001600160a01b038216820361014157565b9181601f840112156101415782359167ffffffffffffffff8311610141576020808501948460051b01011161014157565b34610141576020806003193601126101415760043567ffffffffffffffff8111610141576101d5903690600401610172565b6000913383528160051b91835b83810361021f5750848495849552526040377fc45e3a0dd412bcad8d62398d74d66b1c8449f38beb10da275e4da0c6d3a811a4339160400183a280f35b8086918401358083526001603f88209160ff161b8154179055016101e2565b346101415760203660031901126101415761025761012b565b3001543d5260203df35b9181601f840112156101415782359167ffffffffffffffff8311610141576020838186019501011161014157565b3461014157604080600319360112610141576102a961012b565b602491823567ffffffffffffffff8111610141576102cc60049136908301610261565b85929192308601948554947ffa181078c7d1d4d369301511d3c5611e9367d0cebbf65eefdee9dfc75849c1d33d526020988991898352878452606095863d2085527f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f87528360807f301013e8a31863902646dc218ecd889c37491c2967a8104d5ff1cf42af0f9ea481527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660a0524660c0523060e05260a0892082526119013d526042601e209887841461045b575b506041831461041d575b5050630b135d3f60e11b94600097868952895283528060445280606492833701858a5afa91511416156104125750506001905b60001943014060e01c01018091556000527f997a42216df16c8b9e7caf2fc71c59dba956f1f2b12320f87a80a5879464217d826000a26000f35b638baa579f9052601cfd5b9091929350873d528284873780513d1a82526001906000825afa518a183d15171561044c5790849183386103a5565b505050505050506001906103d8565b3d8a90528685013560ff81901c601b018852853589526001600160ff1b0316905292935090919050836001826000825afa518b183d1517156104a25790838693923861039b565b50505050505050506001906103d8565b3461014157600036600319011261014157600f3d5360e060205275154d756c746963616c6c6572576974685369676e657260f55261012060405261013161012152466060523060805261016060c0526101803df35b346101415760608060031936011261014157600467ffffffffffffffff81358181116101415761053a9036908401610172565b929091610545610146565b9260449182359081116101415761055f9036908501610261565b9390968660051b947f12b047058eea3df4085cdc159a103d9c100c4e78cfb7029cc39d02cb8b9e48f53d52602098878a5289604096888789378888208852308a01548552816080803d208a527f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f87527f301013e8a31863902646dc218ecd889c37491c2967a8104d5ff1cf42af0f9ea481527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660a0524660c0523060e05260a0872082526119013d526042601e20968a8714610708575b50604186146106d6575b5050630b135d3f60e11b9260009584875287528460249586938b85525280606492833701858b5afa91511416156104125750505b600094848652855b8481036106b757509083918787987fc45e3a0dd412bcad8d62398d74d66b1c8449f38beb10da275e4da0c6d3a811a49798525282370183a280f35b8088918401358083526001603f8a209160ff161b81541790550161067c565b863d5285858b3780513d1a82526001906000825afa518a183d1517156106fd578138610640565b505050505050610674565b3d8890528585013560ff81901c601b01865286358c526001600160ff1b031690529050826001826000825afa518b183d15171561074757829038610636565b50505050505050610674565b60c03660031901126101415767ffffffffffffffff60043581811161014157610780903690600401610172565b90916024358181116101415761079a903690600401610172565b604494919435838111610141576107b5903690600401610172565b916107be61015c565b9460a435908111610141576107d7903690600401610261565b97909660643595610843565b3461014157600080600319360112610836576020903033016001815460001943014060e01c01018091558152337f997a42216df16c8b9e7caf2fc71c59dba956f1f2b12320f87a80a5879464217d8383a2f35b80fd5b3d54600c5260203df35b969893949195929790976060928114818a141615610b2a573d5460011615610b1d5760051b9384863d37843d20913d5b868103610b015750853d20868a3d37863d207ffb989fd34c8af81a76f18167f528fc7315f92cacc19a0e63215abd54633f8a283d528c602052604052845260809283528460a052308b015460c05260e03d206040527f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f84527f301013e8a31863902646dc218ecd889c37491c2967a8104d5ff1cf42af0f9ea483527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660a0524660c0523060e05260a084206020526119013d526042601e209260408314610ac2575b60418314610a91575b50630b135d3f60e11b602060009382855260049586528d856024958693604085528060445280606492833701915afa91511416156104125750505b8760205281604052603f918260202090600182549160ff161b90808216610a8357179055600097602089526001602052807fc45e3a0dd412bcad8d62398d74d66b1c8449f38beb10da275e4da0c6d3a811a4838ba28315610a7a5781969395961b885587604093868860403786604001965b8551890190838060206040840194803591829101863784603f198b8d0181013590888d0101355af115610a71576020918188523d90523d848683013e85601f19913d010116950197878914610a61579794610a0b565b826040878c602052600183550190f35b833d81803e3d90fd5b60408985602052f35b638baa579f6000526004601cfd5b6001602091853d52848460403780513d1a83526000825afa518b183d151715610aba573861095e565b505050610999565b3d84905260208281013560ff81901c601b01825283356040526001600160ff1b031686526001826000825afa518c183d15176109555750505050610999565b80602080928b01358b0180359182910183378120815201610873565b63ab143c063d526004601cfd5b633b800a463d526004601cfdfea2646970667358221220b683a260ba0dddc73675b5b1b1f3f56075ddbb13397d349dfdb5e7fc3e1e3bb164736f6c63430008120033";
/**
* @dev The salt for the multicaller with signer to be deployed via
* 0age's immutable create2 factory.
*/
bytes32 internal constant MULTICALLER_WITH_SIGNER_CREATE2_SALT =
0x0000000000000000000000000000000000000000d7eebd756f8ae3022dc33bdb;
// =============================================================
// OPERATIONS
// =============================================================
/**
* @dev Returns the multicaller.
*/
function multicaller() internal returns (Multicaller deployment) {
address expectedDeployment = LibMulticaller.MULTICALLER;
if (_extcodesize(expectedDeployment) == 0) {
bytes32 salt = MULTICALLER_CREATE2_SALT;
address d = _safeCreate2(salt, MULTICALLER_INITCODE);
require(d == expectedDeployment, "Unable to etch Multicaller.");
deployment = Multicaller(payable(d));
}
}
/**
* @dev Returns the multicaller with sender.
*/
function multicallerWithSender() internal returns (MulticallerWithSender deployment) {
address expectedDeployment = LibMulticaller.MULTICALLER_WITH_SENDER;
if (_extcodesize(expectedDeployment) == 0) {
bytes32 salt = MULTICALLER_WITH_SENDER_CREATE2_SALT;
address d = _safeCreate2(salt, MULTICALLER_WITH_SENDER_INITCODE);
require(d == expectedDeployment, "Unable to etch MulticallerWithSender.");
deployment = MulticallerWithSender(payable(d));
}
}
/**
* @dev Returns the multicaller with signer.
*/
function multicallerWithSigner() internal returns (MulticallerWithSigner deployment) {
address expectedDeployment = LibMulticaller.MULTICALLER_WITH_SIGNER;
if (_extcodesize(expectedDeployment) == 0) {
bytes32 salt = MULTICALLER_WITH_SIGNER_CREATE2_SALT;
address d = _safeCreate2(salt, MULTICALLER_WITH_SIGNER_INITCODE);
require(d == expectedDeployment, "Unable to etch MulticallerWithSigner.");
deployment = MulticallerWithSigner(payable(d));
}
}
// =============================================================
// PRIVATE HELPERS
// =============================================================
/**
* @dev Deploys a contract via 0age's immutable create 2 factory for testing.
*/
function _safeCreate2(bytes32 salt, bytes memory initializationCode)
private
returns (address deployment)
{
// Canonical address of 0age's immutable create 2 factory.
address c2f = 0x0000000000FFe8B47B3e2130213B802212439497;
if (_extcodesize(c2f) == 0) {
bytes memory ic2fBytecode =
hex"60806040526004361061003f5760003560e01c806308508b8f1461004457806364e030871461009857806385cf97ab14610138578063a49a7c90146101bc575b600080fd5b34801561005057600080fd5b506100846004803603602081101561006757600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166101ec565b604080519115158252519081900360200190f35b61010f600480360360408110156100ae57600080fd5b813591908101906040810160208201356401000000008111156100d057600080fd5b8201836020820111156100e257600080fd5b8035906020019184600183028401116401000000008311171561010457600080fd5b509092509050610217565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561014457600080fd5b5061010f6004803603604081101561015b57600080fd5b8135919081019060408101602082013564010000000081111561017d57600080fd5b82018360208201111561018f57600080fd5b803590602001918460018302840111640100000000831117156101b157600080fd5b509092509050610592565b3480156101c857600080fd5b5061010f600480360360408110156101df57600080fd5b508035906020013561069e565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205460ff1690565b600083606081901c33148061024c57507fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008116155b6102a1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260458152602001806107746045913960600191505060405180910390fd5b606084848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920182905250604051855195965090943094508b93508692506020918201918291908401908083835b6020831061033557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016102f8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff018019909216911617905260408051929094018281037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00183528085528251928201929092207fff000000000000000000000000000000000000000000000000000000000000008383015260609890981b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016602183015260358201969096526055808201979097528251808203909701875260750182525084519484019490942073ffffffffffffffffffffffffffffffffffffffff81166000908152938490529390922054929350505060ff16156104a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603f815260200180610735603f913960400191505060405180910390fd5b81602001825188818334f5955050508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161461053a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260468152602001806107b96046913960600191505060405180910390fd5b50505073ffffffffffffffffffffffffffffffffffffffff8116600090815260208190526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790559392505050565b6000308484846040516020018083838082843760408051919093018181037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001825280845281516020928301207fff000000000000000000000000000000000000000000000000000000000000008383015260609990991b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166021820152603581019790975260558088019890985282518088039098018852607590960182525085519585019590952073ffffffffffffffffffffffffffffffffffffffff81166000908152948590529490932054939450505060ff909116159050610697575060005b9392505050565b604080517fff000000000000000000000000000000000000000000000000000000000000006020808301919091523060601b6021830152603582018590526055808301859052835180840390910181526075909201835281519181019190912073ffffffffffffffffffffffffffffffffffffffff81166000908152918290529190205460ff161561072e575060005b9291505056fe496e76616c696420636f6e7472616374206372656174696f6e202d20636f6e74726163742068617320616c7265616479206265656e206465706c6f7965642e496e76616c69642073616c74202d206669727374203230206279746573206f66207468652073616c74206d757374206d617463682063616c6c696e6720616464726573732e4661696c656420746f206465706c6f7920636f6e7472616374207573696e672070726f76696465642073616c7420616e6420696e697469616c697a6174696f6e20636f64652ea265627a7a723058202bdc55310d97c4088f18acf04253db593f0914059f0c781a9df3624dcef0d1cf64736f6c634300050a0032";
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, 0xb4d6c782) // `etch(address,bytes)`.
mstore(add(m, 0x20), c2f)
mstore(add(m, 0x40), 0x40)
let n := mload(ic2fBytecode)
mstore(add(m, 0x60), n)
for { let i := 0 } lt(i, n) { i := add(0x20, i) } {
mstore(add(add(m, 0x80), i), mload(add(add(ic2fBytecode, 0x20), i)))
}
let vmAddress := 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D
if iszero(call(gas(), vmAddress, 0, add(m, 0x1c), add(n, 0x64), 0x00, 0x00)) {
revert(0, 0)
}
}
}
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let n := mload(initializationCode)
mstore(m, 0x64e03087) // `safeCreate2(bytes32,bytes)`.
mstore(add(m, 0x20), salt)
mstore(add(m, 0x40), 0x40)
mstore(add(m, 0x60), n)
// prettier-ignore
for { let i := 0 } lt(i, n) { i := add(i, 0x20) } {
mstore(add(add(m, 0x80), i), mload(add(add(initializationCode, 0x20), i)))
}
if iszero(call(gas(), c2f, 0, add(m, 0x1c), add(n, 0x64), m, 0x20)) {
returndatacopy(m, m, returndatasize())
revert(m, returndatasize())
}
deployment := mload(m)
}
}
/**
* @dev Returns the extcodesize of `deployment`.
*/
function _extcodesize(address deployment) private view returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
result := extcodesize(deployment)
}
}
}
multicaller/src/MulticallerWithSender.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/**
* @title MulticallerWithSender
* @author vectorized.eth
* @notice Contract that allows for efficient aggregation of multiple calls
* in a single transaction, while "forwarding" the `msg.sender`.
*/
contract MulticallerWithSender {
// =============================================================
// ERRORS
// =============================================================
/**
* @dev The lengths of the input arrays are not the same.
*/
error ArrayLengthsMismatch();
/**
* @dev This function does not support reentrancy.
*/
error Reentrancy();
// =============================================================
// CONSTRUCTOR
// =============================================================
constructor() payable {
assembly {
// Throughout this code, we will abuse returndatasize
// in place of zero anywhere before a call to save a bit of gas.
// We will use storage slot zero to store the caller at
// bits [0..159] and reentrancy guard flag at bit 160.
sstore(returndatasize(), shl(160, 1))
}
}
// =============================================================
// AGGREGATION OPERATIONS
// =============================================================
/**
* @dev Returns the address that called `aggregateWithSender` on this contract.
* The value is always the zero address outside a transaction.
*/
receive() external payable {
assembly {
mstore(returndatasize(), and(sub(shl(160, 1), 1), sload(returndatasize())))
return(returndatasize(), 0x20)
}
}
/**
* @dev Aggregates multiple calls in a single transaction.
* This method will set `sender` to the `msg.sender` temporarily
* for the span of its execution.
* This method does not support reentrancy.
* @param targets An array of addresses to call.
* @param data An array of calldata to forward to the targets.
* @param values How much ETH to forward to each target.
* @return An array of the returndata from each call.
*/
function aggregateWithSender(
address[] calldata targets,
bytes[] calldata data,
uint256[] calldata values
) external payable returns (bytes[] memory) {
assembly {
if iszero(and(eq(targets.length, data.length), eq(data.length, values.length))) {
// Store the function selector of `ArrayLengthsMismatch()`.
mstore(returndatasize(), 0x3b800a46)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
if iszero(and(sload(returndatasize()), shl(160, 1))) {
// Store the function selector of `Reentrancy()`.
mstore(returndatasize(), 0xab143c06)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
mstore(returndatasize(), 0x20) // Store the memory offset of the `results`.
mstore(0x20, data.length) // Store `data.length` into `results`.
// Early return if no data.
if iszero(data.length) { return(returndatasize(), 0x40) }
// Set the sender slot temporarily for the span of this transaction.
sstore(returndatasize(), caller())
let results := 0x40
// Left shift by 5 is equivalent to multiplying by 0x20.
data.length := shl(5, data.length)
// Copy the offsets from calldata into memory.
calldatacopy(results, data.offset, data.length)
// Offset into `results`.
let resultsOffset := data.length
// Pointer to the end of `results`.
// Recycle `data.length` to avoid stack too deep.
data.length := add(results, data.length)
for {} 1 {} {
// The offset of the current bytes in the calldata.
let o := add(data.offset, mload(results))
let memPtr := add(resultsOffset, 0x40)
// Copy the current bytes from calldata to the memory.
calldatacopy(
memPtr,
add(o, 0x20), // The offset of the current bytes' bytes.
calldataload(o) // The length of the current bytes.
)
if iszero(
call(
gas(), // Remaining gas.
calldataload(targets.offset), // Address to call.
calldataload(values.offset), // ETH to send.
memPtr, // Start of input calldata in memory.
calldataload(o), // Size of input calldata.
0x00, // We will use returndatacopy instead.
0x00 // We will use returndatacopy instead.
)
) {
// Bubble up the revert if the call reverts.
returndatacopy(0x00, 0x00, returndatasize())
revert(0x00, returndatasize())
}
// Advance the `targets.offset`.
targets.offset := add(targets.offset, 0x20)
// Advance the `values.offset`.
values.offset := add(values.offset, 0x20)
// Append the current `resultsOffset` into `results`.
mstore(results, resultsOffset)
results := add(results, 0x20)
// Append the returndatasize, and the returndata.
mstore(memPtr, returndatasize())
returndatacopy(add(memPtr, 0x20), 0x00, returndatasize())
// Advance the `resultsOffset` by `returndatasize() + 0x20`,
// rounded up to the next multiple of 0x20.
resultsOffset := and(add(add(resultsOffset, returndatasize()), 0x3f), not(0x1f))
if iszero(lt(results, data.length)) { break }
}
// Restore the `sender` slot.
sstore(0, shl(160, 1))
// Direct return.
return(0x00, add(resultsOffset, 0x40))
}
}
}
multicaller/src/MulticallerWithSigner.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/**
* @title MulticallerWithSigner
* @author vectorized.eth
* @notice Contract that allows for efficient aggregation of multiple calls
* in a single transaction, while "forwarding" the `signer`.
*/
contract MulticallerWithSigner {
// =============================================================
// EVENTS
// =============================================================
/**
* @dev Emitted when the `nonces` of `signer` are invalidated.
* @param signer The signer of the signature.
* @param nonces The array of nonces invalidated.
*/
event NoncesInvalidated(address indexed signer, uint256[] nonces);
/**
* @dev Emitted when the nonce salt of `signer` is incremented.
* @param signer The signer of the signature.
* @param newNonceSalt The new nonce salt.
*/
event NonceSaltIncremented(address indexed signer, uint256 newNonceSalt);
/**
* @dev `keccak256("NoncesInvalidated(address,uint256[])")`.
*/
uint256 private constant _NONCES_INVALIDATED_EVENT_SIGNATURE =
0xc45e3a0dd412bcad8d62398d74d66b1c8449f38beb10da275e4da0c6d3a811a4;
/**
* @dev `keccak256("NonceSaltIncremented(address,uint256)")`.
*/
uint256 private constant _NONCE_SALT_INCREMENTED_EVENT_SIGNATURE =
0x997a42216df16c8b9e7caf2fc71c59dba956f1f2b12320f87a80a5879464217d;
// =============================================================
// CONSTANTS
// =============================================================
// These EIP-712 constants are made private to save function dispatch gas.
// If you need them in your code, please copy and paste them.
/**
* @dev For EIP-712 signature digest calculation for the
* `aggregateWithSigner` function.
* `keccak256("AggregateWithSigner(address signer,address[] targets,bytes[] data,uint256[] values,uint256 nonce,uint256 nonceSalt)")`.
*/
bytes32 private constant _AGGREGATE_WITH_SIGNER_TYPEHASH =
0xfb989fd34c8af81a76f18167f528fc7315f92cacc19a0e63215abd54633f8a28;
/**
* @dev For EIP-712 signature digest calculation for the
* `invalidateNoncesForSigner` function.
* `keccak256("InvalidateNoncesForSigner(address signer,uint256[] nonces,uint256 nonceSalt)")`.
*/
bytes32 private constant _INVALIDATE_NONCES_FOR_SIGNER_TYPEHASH =
0x12b047058eea3df4085cdc159a103d9c100c4e78cfb7029cc39d02cb8b9e48f5;
/**
* @dev For EIP-712 signature digest calculation for the
* `incrementNonceSaltForSigner` function.
* `keccak256("IncrementNonceSaltForSigner(address signer,uint256 nonceSalt)")`.
*/
bytes32 private constant _INCREMENT_NONCE_SALT_FOR_SIGNER_TYPEHASH =
0xfa181078c7d1d4d369301511d3c5611e9367d0cebbf65eefdee9dfc75849c1d3;
/**
* @dev For EIP-712 signature digest calculation.
* `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`.
*/
bytes32 private constant _DOMAIN_TYPEHASH =
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
/**
* @dev For EIP-712 signature digest calculation.
* `keccak256("MulticallerWithSigner")`.
*/
bytes32 private constant _NAME_HASH =
0x301013e8a31863902646dc218ecd889c37491c2967a8104d5ff1cf42af0f9ea4;
/**
* @dev For EIP-712 signature digest calculation.
* `keccak256("1")`.
*/
bytes32 private constant _VERSION_HASH =
0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6;
// =============================================================
// ERRORS
// =============================================================
/**
* @dev The lengths of the input arrays are not the same.
*/
error ArrayLengthsMismatch();
/**
* @dev This function does not support reentrancy.
*/
error Reentrancy();
/**
* @dev The signature is invalid: it must be correctly signed by the signer,
* with the correct data, an unused nonce, and the signer's current nonce salt.
*/
error InvalidSignature();
// =============================================================
// CONSTRUCTOR
// =============================================================
constructor() payable {
assembly {
// Throughout this code, we will abuse returndatasize
// in place of zero anywhere before a call to save a bit of gas.
// We will use storage slot zero to store the signer at
// bits [96..255] and reentrancy guard at bit 1.
sstore(returndatasize(), 1)
}
}
// =============================================================
// AGGREGATION OPERATIONS
// =============================================================
/**
* @dev Returns the signer passed into `aggregateWithSigner` on this contract.
* The value is always the zero address outside a transaction.
*/
receive() external payable {
assembly {
mstore(0x0c, sload(returndatasize()))
return(returndatasize(), 0x20)
}
}
/**
* @dev Aggregates multiple calls in a single transaction.
* This method will store the `signer` temporarily
* for the span of its execution.
* This method does not support reentrancy.
* Emits a `NoncesInvalidated(signer, [nonce])` event.
* @param targets An array of addresses to call.
* @param data An array of calldata to forward to the targets.
* @param values How much ETH to forward to each target.
* @param nonce The nonce for the signature.
* @param signer The signer of the signature.
* @param signature The signature by the signer.
* @return An array of the returndata from each call.
*/
function aggregateWithSigner(
address[] calldata targets,
bytes[] calldata data,
uint256[] calldata values,
uint256 nonce,
address signer,
bytes calldata signature
) external payable returns (bytes[] memory) {
assembly {
if iszero(and(eq(targets.length, data.length), eq(data.length, values.length))) {
mstore(returndatasize(), 0x3b800a46) // `ArrayLengthsMismatch()`.
revert(0x1c, 0x04)
}
if iszero(and(1, sload(returndatasize()))) {
mstore(returndatasize(), 0xab143c06) // `Reentrancy()`.
revert(0x1c, 0x04)
}
// Multiply `data.length` by 0x20 to give the byte length of the `data` offsets array.
// This is the also the byte length of the `targets` array and `values` array.
data.length := shl(5, data.length)
/* -------------------- CHECK SIGNATURE --------------------- */
// Compute `keccak256(abi.encodePacked(values))`.
calldatacopy(returndatasize(), values.offset, data.length)
let valuesHash := keccak256(returndatasize(), data.length)
// Compute `keccak256(abi.encodePacked(keccak256(data[0]), ..))`.
for { let i := returndatasize() } iszero(eq(i, data.length)) { i := add(i, 0x20) } {
let o := add(data.offset, calldataload(add(data.offset, i)))
calldatacopy(i, add(o, 0x20), calldataload(o))
mstore(i, keccak256(i, calldataload(o)))
}
let dataHash := keccak256(returndatasize(), data.length)
// Compute `keccak256(abi.encodePacked(targets))`.
calldatacopy(returndatasize(), targets.offset, data.length)
let targetsHash := keccak256(returndatasize(), data.length)
// Layout the fields of the struct hash.
mstore(returndatasize(), _AGGREGATE_WITH_SIGNER_TYPEHASH)
mstore(0x20, signer)
mstore(0x40, targetsHash)
mstore(0x60, dataHash)
mstore(0x80, valuesHash)
mstore(0xa0, nonce)
mstore(0xc0, sload(add(signer, address()))) // Store the nonce salt.
mstore(0x40, keccak256(returndatasize(), 0xe0)) // Compute and store the struct hash.
// Layout the fields of the domain separator.
mstore(0x60, _DOMAIN_TYPEHASH)
mstore(0x80, _NAME_HASH)
mstore(0xa0, _VERSION_HASH)
mstore(0xc0, chainid())
mstore(0xe0, address())
mstore(0x20, keccak256(0x60, 0xa0)) // Compute and store the domain separator.
// Layout the fields of `ecrecover`.
mstore(returndatasize(), 0x1901) // Store "\x19\x01".
let digest := keccak256(0x1e, 0x42) // Compute the digest.
for {} 1 {} {
if eq(signature.length, 64) {
mstore(returndatasize(), digest) // Store the digest.
let vs := calldataload(add(signature.offset, 0x20))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, calldataload(signature.offset)) // `r`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t := staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { break }
}
if eq(signature.length, 65) {
mstore(returndatasize(), digest) // Store the digest.
calldatacopy(0x40, signature.offset, signature.length) // Copy `r`, `s`, `v`.
mstore(0x20, byte(returndatasize(), mload(0x80))) // `v`.
let t := staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { break }
}
// ERC1271 fallback.
let f := shl(224, 0x1626ba7e) // `isValidSignature(bytes32,bytes)`.
mstore(0x00, f)
mstore(0x04, digest)
mstore(0x24, 0x40)
mstore(0x44, signature.length)
calldatacopy(0x64, signature.offset, signature.length)
let t := staticcall(gas(), signer, 0x00, add(signature.length, 0x64), 0x24, 0x20)
if iszero(and(eq(mload(0x24), f), t)) {
mstore(0x00, 0x8baa579f) // `InvalidSignature()`.
revert(0x1c, 0x04)
}
break
}
// Check the nonce.
mstore(0x20, signer)
mstore(0x40, nonce)
let bucketSlot := keccak256(0x20, 0x3f)
let bucketValue := sload(bucketSlot)
let bit := shl(and(0xff, nonce), 1)
if and(bit, bucketValue) {
mstore(0x00, 0x8baa579f) // `InvalidSignature()`.
revert(0x1c, 0x04)
}
sstore(bucketSlot, or(bucketValue, bit)) // Invalidate the nonce.
// Emit `NoncesInvalidated(signer, [nonce])`.
mstore(0x00, 0x20)
mstore(0x20, 1)
// The nonce is already at 0x40.
log2(0x00, 0x60, _NONCES_INVALIDATED_EVENT_SIGNATURE, signer)
/* ------------------- PERFORM AGGREGATE -------------------- */
// Early return if no data.
if iszero(data.length) {
// Slot 0x00's value is already 0x20.
mstore(0x20, data.length) // Store `data.length` into `results`.
return(0x00, 0x40)
}
// Set the signer slot temporarily for the span of this transaction.
sstore(0, shl(96, signer))
let results := 0x40
// Copy the offsets from calldata into memory.
calldatacopy(results, data.offset, data.length)
// Offset into `results`.
let resultsOffset := data.length
// Pointer to the end of `results`.
let end := add(results, data.length)
// For deriving the calldata offsets from the `results` pointer.
let valuesOffsetDiff := sub(values.offset, results)
let targetsOffsetDiff := sub(targets.offset, results)
for {} 1 {} {
// The offset of the current bytes in the calldata.
let o := add(data.offset, mload(results))
let memPtr := add(resultsOffset, 0x40)
// Copy the current bytes from calldata to the memory.
calldatacopy(
memPtr,
add(o, 0x20), // The offset of the current bytes' bytes.
calldataload(o) // The length of the current bytes.
)
if iszero(
call(
gas(), // Remaining gas.
calldataload(add(targetsOffsetDiff, results)), // Address to call.
calldataload(add(valuesOffsetDiff, results)), // ETH to send.
memPtr, // Start of input calldata in memory.
calldataload(o), // Size of input calldata.
0x00, // We will use returndatacopy instead.
0x00 // We will use returndatacopy instead.
)
) {
// Bubble up the revert if the call reverts.
returndatacopy(0x00, 0x00, returndatasize())
revert(0x00, returndatasize())
}
// Append the current `resultsOffset` into `results`.
mstore(results, resultsOffset)
// Append the returndatasize, and the returndata.
mstore(memPtr, returndatasize())
returndatacopy(add(memPtr, 0x20), 0x00, returndatasize())
// Advance the `resultsOffset` by `returndatasize() + 0x20`,
// rounded up to the next multiple of 0x20.
resultsOffset := and(add(add(resultsOffset, returndatasize()), 0x3f), not(0x1f))
// Advance the `results` pointer.
results := add(results, 0x20)
if eq(results, end) { break }
}
// Slot 0x00's value is already 0x20.
mstore(0x20, targets.length) // Store `targets.length` into `results`.
// Restore the `signer` slot.
sstore(0, 1)
// Direct return.
return(0x00, add(resultsOffset, 0x40))
}
}
// =============================================================
// SIGNATURE OPERATIONS
// =============================================================
/**
* @dev Invalidates the `nonces` of `msg.sender`.
* Emits a `NoncesInvalidated(msg.sender, nonces)` event.
* @param nonces An array of nonces to invalidate.
*/
function invalidateNonces(uint256[] calldata nonces) external {
assembly {
mstore(0x00, caller())
// Iterate through all the nonces and set their boolean values in the storage.
let end := shl(5, nonces.length)
for { let i := 0 } iszero(eq(i, end)) { i := add(i, 0x20) } {
let nonce := calldataload(add(nonces.offset, i))
mstore(0x20, nonce)
let bucketSlot := keccak256(0x00, 0x3f)
sstore(bucketSlot, or(sload(bucketSlot), shl(and(0xff, nonce), 1)))
}
// Emit `NoncesInvalidated(msg.sender, nonces)`.
mstore(0x00, 0x20)
mstore(0x20, nonces.length)
calldatacopy(0x40, nonces.offset, end)
log2(0x00, add(0x40, end), _NONCES_INVALIDATED_EVENT_SIGNATURE, caller())
}
}
/**
* @dev Invalidates the `nonces` of `signer`.
* Emits a `NoncesInvalidated(signer, nonces)` event.
* @param nonces An array of nonces to invalidate.
* @param signer The signer of the signature.
* @param signature The signature by the signer.
*/
function invalidateNoncesForSigner(
uint256[] calldata nonces,
address signer,
bytes calldata signature
) external {
assembly {
let end := shl(5, nonces.length)
// Layout the fields of the struct hash.
mstore(returndatasize(), _INVALIDATE_NONCES_FOR_SIGNER_TYPEHASH)
mstore(0x20, signer)
// Compute and store `keccak256(abi.encodePacked(nonces))`.
calldatacopy(0x40, nonces.offset, end)
mstore(0x40, keccak256(0x40, end))
mstore(0x60, sload(add(signer, address()))) // Store the nonce salt.
mstore(0x40, keccak256(returndatasize(), 0x80)) // Compute and store the struct hash.
// Layout the fields of the domain separator.
mstore(0x60, _DOMAIN_TYPEHASH)
mstore(0x80, _NAME_HASH)
mstore(0xa0, _VERSION_HASH)
mstore(0xc0, chainid())
mstore(0xe0, address())
mstore(0x20, keccak256(0x60, 0xa0)) // Compute and store the domain separator.
// Layout the fields of `ecrecover`.
mstore(returndatasize(), 0x1901) // Store "\x19\x01".
let digest := keccak256(0x1e, 0x42) // Compute the digest.
for {} 1 {} {
if eq(signature.length, 64) {
mstore(returndatasize(), digest) // Store the digest.
let vs := calldataload(add(signature.offset, 0x20))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, calldataload(signature.offset)) // `r`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t := staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { break }
}
if eq(signature.length, 65) {
mstore(returndatasize(), digest) // Store the digest.
calldatacopy(0x40, signature.offset, signature.length) // Copy `r`, `s`, `v`.
mstore(0x20, byte(returndatasize(), mload(0x80))) // `v`.
let t := staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { break }
}
// ERC1271 fallback.
let f := shl(224, 0x1626ba7e) // `isValidSignature(bytes32,bytes)`.
mstore(0x00, f)
mstore(0x04, digest)
mstore(0x24, 0x40)
mstore(0x44, signature.length)
calldatacopy(0x64, signature.offset, signature.length)
let t := staticcall(gas(), signer, 0x00, add(signature.length, 0x64), 0x24, 0x20)
if iszero(and(eq(mload(0x24), f), t)) {
mstore(0x00, 0x8baa579f) // `InvalidSignature()`.
revert(0x1c, 0x04)
}
break
}
mstore(0x00, signer)
// Iterate through all the nonces and set their boolean values in the storage.
for { let i := 0 } iszero(eq(i, end)) { i := add(i, 0x20) } {
let nonce := calldataload(add(nonces.offset, i))
mstore(0x20, nonce)
let bucketSlot := keccak256(0x00, 0x3f)
sstore(bucketSlot, or(sload(bucketSlot), shl(and(0xff, nonce), 1)))
}
// Emit `NoncesInvalidated(signer, nonces)`.
mstore(0x00, 0x20)
mstore(0x20, nonces.length)
calldatacopy(0x40, nonces.offset, end)
log2(0x00, add(0x40, end), _NONCES_INVALIDATED_EVENT_SIGNATURE, signer)
}
}
/**
* @dev Returns whether each of the `nonces` of `signer` has been invalidated.
* @param signer The signer of the signature.
* @param nonces An array of nonces.
* @return A bool array representing whether each nonce has been invalidated.
*/
function noncesInvalidated(address signer, uint256[] calldata nonces)
external
view
returns (bool[] memory)
{
assembly {
mstore(0x00, signer)
// Iterate through all the nonces and append their boolean values.
let end := shl(5, nonces.length)
for { let i := 0 } iszero(eq(i, end)) { i := add(i, 0x20) } {
let nonce := calldataload(add(nonces.offset, i))
mstore(0x20, nonce)
let bit := and(1, shr(and(0xff, nonce), sload(keccak256(0x00, 0x3f))))
mstore(add(0x40, i), bit)
}
mstore(0x00, 0x20) // Store the memory offset of the `results`.
mstore(0x20, nonces.length) // Store `data.length` into `results`.
return(0x00, add(0x40, end))
}
}
/**
* @dev Increments the nonce salt of `msg.sender`.
* For making all unused signatures with the current nonce salt invalid.
* Will NOT make invalidated nonces available for use.
* Emits a `NonceSaltIncremented(msg.sender, newNonceSalt)` event.
* @return The new nonce salt.
*/
function incrementNonceSalt() external returns (uint256) {
assembly {
let nonceSaltSlot := add(caller(), address())
// Increment by some pseudorandom amount from [1..4294967296].
let nonceSalt := sload(nonceSaltSlot)
let newNonceSalt := add(add(1, shr(224, blockhash(sub(number(), 1)))), nonceSalt)
sstore(nonceSaltSlot, newNonceSalt)
// Emit `NonceSaltIncremented(msg.sender, newNonceSalt)`.
mstore(0x00, newNonceSalt)
log2(0x00, 0x20, _NONCE_SALT_INCREMENTED_EVENT_SIGNATURE, caller())
return(0x00, 0x20)
}
}
/**
* @dev Increments the nonce salt of `signer`.
* For making all unused signatures with the current nonce salt invalid.
* Will NOT make invalidated nonces available for use.
* Emits a `NonceSaltIncremented(signer, newNonceSalt)` event.
* @param signer The signer of the signature.
* @param signature The signature by the signer.
* @return The new nonce salt.
*/
function incrementNonceSaltForSigner(address signer, bytes calldata signature)
external
returns (uint256)
{
assembly {
let nonceSaltSlot := add(signer, address())
let nonceSalt := sload(nonceSaltSlot)
// Layout the fields of the struct hash.
mstore(returndatasize(), _INCREMENT_NONCE_SALT_FOR_SIGNER_TYPEHASH)
mstore(0x20, signer)
mstore(0x40, nonceSalt) // Store the nonce salt.
mstore(0x40, keccak256(returndatasize(), 0x60)) // Compute and store the struct hash.
// Layout the fields of the domain separator.
mstore(0x60, _DOMAIN_TYPEHASH)
mstore(0x80, _NAME_HASH)
mstore(0xa0, _VERSION_HASH)
mstore(0xc0, chainid())
mstore(0xe0, address())
mstore(0x20, keccak256(0x60, 0xa0)) // Compute and store the domain separator.
// Layout the fields of `ecrecover`.
mstore(returndatasize(), 0x1901) // Store "\x19\x01".
let digest := keccak256(0x1e, 0x42) // Compute the digest.
for {} 1 {} {
if eq(signature.length, 64) {
mstore(returndatasize(), digest) // Store the digest.
let vs := calldataload(add(signature.offset, 0x20))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, calldataload(signature.offset)) // `r`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t := staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { break }
}
if eq(signature.length, 65) {
mstore(returndatasize(), digest) // Store the digest.
calldatacopy(0x40, signature.offset, signature.length) // Copy `r`, `s`, `v`.
mstore(0x20, byte(returndatasize(), mload(0x80))) // `v`.
let t := staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { break }
}
// ERC1271 fallback.
let f := shl(224, 0x1626ba7e) // `isValidSignature(bytes32,bytes)`.
mstore(0x00, f)
mstore(0x04, digest)
mstore(0x24, 0x40)
mstore(0x44, signature.length)
calldatacopy(0x64, signature.offset, signature.length)
let t := staticcall(gas(), signer, 0x00, add(signature.length, 0x64), 0x24, 0x20)
if iszero(and(eq(mload(0x24), f), t)) {
mstore(0x00, 0x8baa579f) // `InvalidSignature()`.
revert(0x1c, 0x04)
}
break
}
// Increment by some pseudorandom amount from [1..4294967296].
let newNonceSalt := add(add(1, shr(224, blockhash(sub(number(), 1)))), nonceSalt)
sstore(nonceSaltSlot, newNonceSalt)
// Emit `NonceSaltIncremented(signer, newNonceSalt)`.
mstore(0x00, newNonceSalt)
log2(0x00, 0x20, _NONCE_SALT_INCREMENTED_EVENT_SIGNATURE, signer)
return(0x00, 0x20)
}
}
/**
* @dev Returns the nonce salt of `signer`.
* @param signer The signer of the signature.
* @return The current nonce salt of `signer`.
*/
function nonceSaltOf(address signer) external view returns (uint256) {
assembly {
mstore(returndatasize(), sload(add(signer, address())))
return(returndatasize(), 0x20)
}
}
/**
* @dev Returns the EIP-712 domain information, as specified in
* [EIP-5267](https://eips.ethereum.org/EIPS/eip-5267).
* @return fields `hex"0f"` (`0b01111`).
* @return name `"MulticallerWithSigner"`.
* @return version `"1"`.
* @return chainId The chain ID which this contract is on.
* @return verifyingContract `address(this)`, the address of this contract.
* @return salt `bytes32(0)` (not used).
* @return extensions `[]` (not used).
*/
function eip712Domain()
external
view
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
)
{
assembly {
pop(fields)
mstore8(returndatasize(), 0x0f)
pop(name)
mstore(0x20, 0xe0)
mstore(0xf5, 0x154d756c746963616c6c6572576974685369676e6572)
pop(version)
mstore(0x40, 0x120)
mstore(0x121, 0x0131)
pop(chainId)
mstore(0x60, chainid())
pop(verifyingContract)
mstore(0x80, address())
pop(salt)
pop(extensions)
mstore(0xc0, 0x160)
return(returndatasize(), 0x180)
}
}
}
solady/src/tokens/ERC20.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple ERC20 + EIP-2612 implementation.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol)
///
/// @dev Note:
/// - The ERC20 standard allows minting and transferring to and from the zero address,
/// minting and transferring zero tokens, as well as self-approvals.
/// For performance, this implementation WILL NOT revert for such actions.
/// Please add any checks with overrides if desired.
/// - The `permit` function uses the ecrecover precompile (0x1).
///
/// If you are overriding:
/// - NEVER violate the ERC20 invariant:
/// the total sum of all balances must be equal to `totalSupply()`.
/// - Check that the overridden function is actually used in the function you want to
/// change the behavior of. Much of the code has been manually inlined for performance.
abstract contract ERC20 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The total supply has overflowed.
error TotalSupplyOverflow();
/// @dev The allowance has overflowed.
error AllowanceOverflow();
/// @dev The allowance has underflowed.
error AllowanceUnderflow();
/// @dev Insufficient balance.
error InsufficientBalance();
/// @dev Insufficient allowance.
error InsufficientAllowance();
/// @dev The permit is invalid.
error InvalidPermit();
/// @dev The permit has expired.
error PermitExpired();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Emitted when `amount` tokens is transferred from `from` to `to`.
event Transfer(address indexed from, address indexed to, uint256 amount);
/// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`.
event Approval(address indexed owner, address indexed spender, uint256 amount);
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
/// @dev `keccak256(bytes("Approval(address,address,uint256)"))`.
uint256 private constant _APPROVAL_EVENT_SIGNATURE =
0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The storage slot for the total supply.
uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c;
/// @dev The balance slot of `owner` is given by:
/// ```
/// mstore(0x0c, _BALANCE_SLOT_SEED)
/// mstore(0x00, owner)
/// let balanceSlot := keccak256(0x0c, 0x20)
/// ```
uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2;
/// @dev The allowance slot of (`owner`, `spender`) is given by:
/// ```
/// mstore(0x20, spender)
/// mstore(0x0c, _ALLOWANCE_SLOT_SEED)
/// mstore(0x00, owner)
/// let allowanceSlot := keccak256(0x0c, 0x34)
/// ```
uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20;
/// @dev The nonce slot of `owner` is given by:
/// ```
/// mstore(0x0c, _NONCES_SLOT_SEED)
/// mstore(0x00, owner)
/// let nonceSlot := keccak256(0x0c, 0x20)
/// ```
uint256 private constant _NONCES_SLOT_SEED = 0x38377508;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev `(_NONCES_SLOT_SEED << 16) | 0x1901`.
uint256 private constant _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX = 0x383775081901;
/// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`.
bytes32 private constant _DOMAIN_TYPEHASH =
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
/// @dev `keccak256("1")`.
bytes32 private constant _VERSION_HASH =
0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6;
/// @dev `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`.
bytes32 private constant _PERMIT_TYPEHASH =
0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 METADATA */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the name of the token.
function name() public view virtual returns (string memory);
/// @dev Returns the symbol of the token.
function symbol() public view virtual returns (string memory);
/// @dev Returns the decimals places of the token.
function decimals() public view virtual returns (uint8) {
return 18;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the amount of tokens in existence.
function totalSupply() public view virtual returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
result := sload(_TOTAL_SUPPLY_SLOT)
}
}
/// @dev Returns the amount of tokens owned by `owner`.
function balanceOf(address owner) public view virtual returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x20))
}
}
/// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`.
function allowance(address owner, address spender)
public
view
virtual
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, spender)
mstore(0x0c, _ALLOWANCE_SLOT_SEED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x34))
}
}
/// @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
///
/// Emits a {Approval} event.
function approve(address spender, uint256 amount) public virtual returns (bool) {
/// @solidity memory-safe-assembly
assembly {
// Compute the allowance slot and store the amount.
mstore(0x20, spender)
mstore(0x0c, _ALLOWANCE_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x34), amount)
// Emit the {Approval} event.
mstore(0x00, amount)
log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c)))
}
return true;
}
/// @dev Transfer `amount` tokens from the caller to `to`.
///
/// Requirements:
/// - `from` must at least have `amount`.
///
/// Emits a {Transfer} event.
function transfer(address to, uint256 amount) public virtual returns (bool) {
_beforeTokenTransfer(msg.sender, to, amount);
/// @solidity memory-safe-assembly
assembly {
// Compute the balance slot and load its value.
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, caller())
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Compute the balance slot of `to`.
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance of `to`.
// Will not overflow because the sum of all user balances
// cannot exceed the maximum uint256 value.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c)))
}
_afterTokenTransfer(msg.sender, to, amount);
return true;
}
/// @dev Transfers `amount` tokens from `from` to `to`.
///
/// Note: Does not update the allowance if it is the maximum uint256 value.
///
/// Requirements:
/// - `from` must at least have `amount`.
/// - The caller must have at least `amount` of allowance to transfer the tokens of `from`.
///
/// Emits a {Transfer} event.
function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
_beforeTokenTransfer(from, to, amount);
/// @solidity memory-safe-assembly
assembly {
let from_ := shl(96, from)
// Compute the allowance slot and load its value.
mstore(0x20, caller())
mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED))
let allowanceSlot := keccak256(0x0c, 0x34)
let allowance_ := sload(allowanceSlot)
// If the allowance is not the maximum uint256 value.
if add(allowance_, 1) {
// Revert if the amount to be transferred exceeds the allowance.
if gt(amount, allowance_) {
mstore(0x00, 0x13be252b) // `InsufficientAllowance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated allowance.
sstore(allowanceSlot, sub(allowance_, amount))
}
// Compute the balance slot and load its value.
mstore(0x0c, or(from_, _BALANCE_SLOT_SEED))
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Compute the balance slot of `to`.
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance of `to`.
// Will not overflow because the sum of all user balances
// cannot exceed the maximum uint256 value.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c)))
}
_afterTokenTransfer(from, to, amount);
return true;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EIP-2612 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev For more performance, override to return the constant value
/// of `keccak256(bytes(name()))` if `name()` will never change.
function _constantNameHash() internal view virtual returns (bytes32 result) {}
/// @dev Returns the current nonce for `owner`.
/// This value is used to compute the signature for EIP-2612 permit.
function nonces(address owner) public view virtual returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
// Compute the nonce slot and load its value.
mstore(0x0c, _NONCES_SLOT_SEED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x20))
}
}
/// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`,
/// authorized by a signed approval by `owner`.
///
/// Emits a {Approval} event.
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
bytes32 nameHash = _constantNameHash();
// We simply calculate it on-the-fly to allow for cases where the `name` may change.
if (nameHash == bytes32(0)) nameHash = keccak256(bytes(name()));
/// @solidity memory-safe-assembly
assembly {
// Revert if the block timestamp is greater than `deadline`.
if gt(timestamp(), deadline) {
mstore(0x00, 0x1a15a3cc) // `PermitExpired()`.
revert(0x1c, 0x04)
}
let m := mload(0x40) // Grab the free memory pointer.
// Clean the upper 96 bits.
owner := shr(96, shl(96, owner))
spender := shr(96, shl(96, spender))
// Compute the nonce slot and load its value.
mstore(0x0e, _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX)
mstore(0x00, owner)
let nonceSlot := keccak256(0x0c, 0x20)
let nonceValue := sload(nonceSlot)
// Prepare the domain separator.
mstore(m, _DOMAIN_TYPEHASH)
mstore(add(m, 0x20), nameHash)
mstore(add(m, 0x40), _VERSION_HASH)
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
mstore(0x2e, keccak256(m, 0xa0))
// Prepare the struct hash.
mstore(m, _PERMIT_TYPEHASH)
mstore(add(m, 0x20), owner)
mstore(add(m, 0x40), spender)
mstore(add(m, 0x60), value)
mstore(add(m, 0x80), nonceValue)
mstore(add(m, 0xa0), deadline)
mstore(0x4e, keccak256(m, 0xc0))
// Prepare the ecrecover calldata.
mstore(0x00, keccak256(0x2c, 0x42))
mstore(0x20, and(0xff, v))
mstore(0x40, r)
mstore(0x60, s)
let t := staticcall(gas(), 1, 0, 0x80, 0x20, 0x20)
// If the ecrecover fails, the returndatasize will be 0x00,
// `owner` will be checked if it equals the hash at 0x00,
// which evaluates to false (i.e. 0), and we will revert.
// If the ecrecover succeeds, the returndatasize will be 0x20,
// `owner` will be compared against the returned address at 0x20.
if iszero(eq(mload(returndatasize()), owner)) {
mstore(0x00, 0xddafbaef) // `InvalidPermit()`.
revert(0x1c, 0x04)
}
// Increment and store the updated nonce.
sstore(nonceSlot, add(nonceValue, t)) // `t` is 1 if ecrecover succeeds.
// Compute the allowance slot and store the value.
// The `owner` is already at slot 0x20.
mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender))
sstore(keccak256(0x2c, 0x34), value)
// Emit the {Approval} event.
log3(add(m, 0x60), 0x20, _APPROVAL_EVENT_SIGNATURE, owner, spender)
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero pointer.
}
}
/// @dev Returns the EIP-712 domain separator for the EIP-2612 permit.
function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) {
bytes32 nameHash = _constantNameHash();
// We simply calculate it on-the-fly to allow for cases where the `name` may change.
if (nameHash == bytes32(0)) nameHash = keccak256(bytes(name()));
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Grab the free memory pointer.
mstore(m, _DOMAIN_TYPEHASH)
mstore(add(m, 0x20), nameHash)
mstore(add(m, 0x40), _VERSION_HASH)
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
result := keccak256(m, 0xa0)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL MINT FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Mints `amount` tokens to `to`, increasing the total supply.
///
/// Emits a {Transfer} event.
function _mint(address to, uint256 amount) internal virtual {
_beforeTokenTransfer(address(0), to, amount);
/// @solidity memory-safe-assembly
assembly {
let totalSupplyBefore := sload(_TOTAL_SUPPLY_SLOT)
let totalSupplyAfter := add(totalSupplyBefore, amount)
// Revert if the total supply overflows.
if lt(totalSupplyAfter, totalSupplyBefore) {
mstore(0x00, 0xe5cfe957) // `TotalSupplyOverflow()`.
revert(0x1c, 0x04)
}
// Store the updated total supply.
sstore(_TOTAL_SUPPLY_SLOT, totalSupplyAfter)
// Compute the balance slot and load its value.
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, mload(0x0c)))
}
_afterTokenTransfer(address(0), to, amount);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL BURN FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Burns `amount` tokens from `from`, reducing the total supply.
///
/// Emits a {Transfer} event.
function _burn(address from, uint256 amount) internal virtual {
_beforeTokenTransfer(from, address(0), amount);
/// @solidity memory-safe-assembly
assembly {
// Compute the balance slot and load its value.
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, from)
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Subtract and store the updated total supply.
sstore(_TOTAL_SUPPLY_SLOT, sub(sload(_TOTAL_SUPPLY_SLOT), amount))
// Emit the {Transfer} event.
mstore(0x00, amount)
log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0)
}
_afterTokenTransfer(from, address(0), amount);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL TRANSFER FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Moves `amount` of tokens from `from` to `to`.
function _transfer(address from, address to, uint256 amount) internal virtual {
_beforeTokenTransfer(from, to, amount);
/// @solidity memory-safe-assembly
assembly {
let from_ := shl(96, from)
// Compute the balance slot and load its value.
mstore(0x0c, or(from_, _BALANCE_SLOT_SEED))
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Compute the balance slot of `to`.
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance of `to`.
// Will not overflow because the sum of all user balances
// cannot exceed the maximum uint256 value.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c)))
}
_afterTokenTransfer(from, to, amount);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL ALLOWANCE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Updates the allowance of `owner` for `spender` based on spent `amount`.
function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute the allowance slot and load its value.
mstore(0x20, spender)
mstore(0x0c, _ALLOWANCE_SLOT_SEED)
mstore(0x00, owner)
let allowanceSlot := keccak256(0x0c, 0x34)
let allowance_ := sload(allowanceSlot)
// If the allowance is not the maximum uint256 value.
if add(allowance_, 1) {
// Revert if the amount to be transferred exceeds the allowance.
if gt(amount, allowance_) {
mstore(0x00, 0x13be252b) // `InsufficientAllowance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated allowance.
sstore(allowanceSlot, sub(allowance_, amount))
}
}
}
/// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`.
///
/// Emits a {Approval} event.
function _approve(address owner, address spender, uint256 amount) internal virtual {
/// @solidity memory-safe-assembly
assembly {
let owner_ := shl(96, owner)
// Compute the allowance slot and store the amount.
mstore(0x20, spender)
mstore(0x0c, or(owner_, _ALLOWANCE_SLOT_SEED))
sstore(keccak256(0x0c, 0x34), amount)
// Emit the {Approval} event.
mstore(0x00, amount)
log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, owner_), shr(96, mload(0x2c)))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HOOKS TO OVERRIDE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Hook that is called before any transfer of tokens.
/// This includes minting and burning.
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}
/// @dev Hook that is called after any transfer of tokens.
/// This includes minting and burning.
function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
}
solady/src/utils/EfficientHashLib.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Library for efficiently performing keccak256 hashes.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/EfficientHashLib.sol)
/// @dev To avoid stack-too-deep, you can use:
/// ```
/// bytes32[] memory buffer = EfficientHashLib.malloc(10);
/// EfficientHashLib.set(buffer, 0, value0);
/// ..
/// EfficientHashLib.set(buffer, 9, value9);
/// bytes32 finalHash = EfficientHashLib.hash(buffer);
/// ```
library EfficientHashLib {
/// @dev Returns `keccak256(abi.encode(value0))`.
function hash(bytes32 value0) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, value0)
result := keccak256(0x00, 0x20)
}
}
/// @dev Returns `keccak256(abi.encode(value0))`.
function hash(uint256 value0) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, value0)
result := keccak256(0x00, 0x20)
}
}
/// @dev Returns `keccak256(abi.encode(value0, value1))`.
function hash(bytes32 value0, bytes32 value1) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, value0)
mstore(0x20, value1)
result := keccak256(0x00, 0x40)
}
}
/// @dev Returns `keccak256(abi.encode(value0, value1))`.
function hash(uint256 value0, uint256 value1) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, value0)
mstore(0x20, value1)
result := keccak256(0x00, 0x40)
}
}
/// @dev Returns `keccak256(abi.encode(value0, value1, value2))`.
function hash(bytes32 value0, bytes32 value1, bytes32 value2)
internal
pure
returns (bytes32 result)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, value0)
mstore(add(m, 0x20), value1)
mstore(add(m, 0x40), value2)
result := keccak256(m, 0x60)
}
}
/// @dev Returns `keccak256(abi.encode(value0, value1, value2))`.
function hash(uint256 value0, uint256 value1, uint256 value2)
internal
pure
returns (bytes32 result)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, value0)
mstore(add(m, 0x20), value1)
mstore(add(m, 0x40), value2)
result := keccak256(m, 0x60)
}
}
/// @dev Returns `keccak256(abi.encode(value0, value1, value2, value3))`.
function hash(bytes32 value0, bytes32 value1, bytes32 value2, bytes32 value3)
internal
pure
returns (bytes32 result)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, value0)
mstore(add(m, 0x20), value1)
mstore(add(m, 0x40), value2)
mstore(add(m, 0x60), value3)
result := keccak256(m, 0x80)
}
}
/// @dev Returns `keccak256(abi.encode(value0, value1, value2, value3))`.
function hash(uint256 value0, uint256 value1, uint256 value2, uint256 value3)
internal
pure
returns (bytes32 result)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, value0)
mstore(add(m, 0x20), value1)
mstore(add(m, 0x40), value2)
mstore(add(m, 0x60), value3)
result := keccak256(m, 0x80)
}
}
/// @dev Returns `keccak256(abi.encode(value0, .., value4))`.
function hash(bytes32 value0, bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4)
internal
pure
returns (bytes32 result)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, value0)
mstore(add(m, 0x20), value1)
mstore(add(m, 0x40), value2)
mstore(add(m, 0x60), value3)
mstore(add(m, 0x80), value4)
result := keccak256(m, 0xa0)
}
}
/// @dev Returns `keccak256(abi.encode(value0, .., value4))`.
function hash(uint256 value0, uint256 value1, uint256 value2, uint256 value3, uint256 value4)
internal
pure
returns (bytes32 result)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, value0)
mstore(add(m, 0x20), value1)
mstore(add(m, 0x40), value2)
mstore(add(m, 0x60), value3)
mstore(add(m, 0x80), value4)
result := keccak256(m, 0xa0)
}
}
/// @dev Returns `keccak256(abi.encode(value0, .., value5))`.
function hash(
bytes32 value0,
bytes32 value1,
bytes32 value2,
bytes32 value3,
bytes32 value4,
bytes32 value5
) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, value0)
mstore(add(m, 0x20), value1)
mstore(add(m, 0x40), value2)
mstore(add(m, 0x60), value3)
mstore(add(m, 0x80), value4)
mstore(add(m, 0xa0), value5)
result := keccak256(m, 0xc0)
}
}
/// @dev Returns `keccak256(abi.encode(value0, .., value5))`.
function hash(
uint256 value0,
uint256 value1,
uint256 value2,
uint256 value3,
uint256 value4,
uint256 value5
) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, value0)
mstore(add(m, 0x20), value1)
mstore(add(m, 0x40), value2)
mstore(add(m, 0x60), value3)
mstore(add(m, 0x80), value4)
mstore(add(m, 0xa0), value5)
result := keccak256(m, 0xc0)
}
}
/// @dev Returns `keccak256(abi.encode(value0, .., value6))`.
function hash(
bytes32 value0,
bytes32 value1,
bytes32 value2,
bytes32 value3,
bytes32 value4,
bytes32 value5,
bytes32 value6
) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, value0)
mstore(add(m, 0x20), value1)
mstore(add(m, 0x40), value2)
mstore(add(m, 0x60), value3)
mstore(add(m, 0x80), value4)
mstore(add(m, 0xa0), value5)
mstore(add(m, 0xc0), value6)
result := keccak256(m, 0xe0)
}
}
/// @dev Returns `keccak256(abi.encode(value0, .., value6))`.
function hash(
uint256 value0,
uint256 value1,
uint256 value2,
uint256 value3,
uint256 value4,
uint256 value5,
uint256 value6
) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, value0)
mstore(add(m, 0x20), value1)
mstore(add(m, 0x40), value2)
mstore(add(m, 0x60), value3)
mstore(add(m, 0x80), value4)
mstore(add(m, 0xa0), value5)
mstore(add(m, 0xc0), value6)
result := keccak256(m, 0xe0)
}
}
/// @dev Returns `keccak256(abi.encode(value0, .., value7))`.
function hash(
bytes32 value0,
bytes32 value1,
bytes32 value2,
bytes32 value3,
bytes32 value4,
bytes32 value5,
bytes32 value6,
bytes32 value7
) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, value0)
mstore(add(m, 0x20), value1)
mstore(add(m, 0x40), value2)
mstore(add(m, 0x60), value3)
mstore(add(m, 0x80), value4)
mstore(add(m, 0xa0), value5)
mstore(add(m, 0xc0), value6)
mstore(add(m, 0xe0), value7)
result := keccak256(m, 0x100)
}
}
/// @dev Returns `keccak256(abi.encode(value0, .., value7))`.
function hash(
uint256 value0,
uint256 value1,
uint256 value2,
uint256 value3,
uint256 value4,
uint256 value5,
uint256 value6,
uint256 value7
) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, value0)
mstore(add(m, 0x20), value1)
mstore(add(m, 0x40), value2)
mstore(add(m, 0x60), value3)
mstore(add(m, 0x80), value4)
mstore(add(m, 0xa0), value5)
mstore(add(m, 0xc0), value6)
mstore(add(m, 0xe0), value7)
result := keccak256(m, 0x100)
}
}
/// @dev Returns `keccak256(abi.encode(buffer[0], .., value[buffer.length - 1]))`.
function hash(bytes32[] memory buffer) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
result := keccak256(add(buffer, 0x20), shl(5, mload(buffer)))
}
}
/// @dev Sets `buffer[i]` to `value`, without a bounds check.
/// Returns the `buffer` for function chaining.
function set(bytes32[] memory buffer, uint256 i, bytes32 value)
internal
pure
returns (bytes32[] memory)
{
/// @solidity memory-safe-assembly
assembly {
mstore(add(buffer, shl(5, add(1, i))), value)
}
return buffer;
}
/// @dev Sets `buffer[i]` to `value`, without a bounds check.
/// Returns the `buffer` for function chaining.
function set(bytes32[] memory buffer, uint256 i, uint256 value)
internal
pure
returns (bytes32[] memory)
{
/// @solidity memory-safe-assembly
assembly {
mstore(add(buffer, shl(5, add(1, i))), value)
}
return buffer;
}
/// @dev Returns `new bytes32[](n)`, without zeroing out the memory.
function malloc(uint256 n) internal pure returns (bytes32[] memory buffer) {
/// @solidity memory-safe-assembly
assembly {
buffer := mload(0x40)
mstore(buffer, n)
mstore(0x40, add(shl(5, add(1, n)), buffer))
}
}
/// @dev Frees memory that has been allocated for `buffer`.
/// No-op if `buffer.length` is zero, or if new memory has been allocated after `buffer`.
function free(bytes32[] memory buffer) internal pure {
/// @solidity memory-safe-assembly
assembly {
let n := mload(buffer)
mstore(shl(6, lt(iszero(n), eq(add(shl(5, add(1, n)), buffer), mload(0x40)))), buffer)
}
}
}
solady/src/utils/LibBit.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Library for bit twiddling and boolean operations.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibBit.sol)
/// @author Inspired by (https://graphics.stanford.edu/~seander/bithacks.html)
library LibBit {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* BIT TWIDDLING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Find last set.
/// Returns the index of the most significant bit of `x`,
/// counting from the least significant bit position.
/// If `x` is zero, returns 256.
function fls(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
r := or(shl(8, iszero(x)), shl(7, lt(0xffffffffffffffffffffffffffffffff, x)))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
// forgefmt: disable-next-item
r := or(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)),
0x0706060506020504060203020504030106050205030304010505030400000000))
}
}
/// @dev Count leading zeros.
/// Returns the number of zeros preceding the most significant one bit.
/// If `x` is zero, returns 256.
function clz(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
// forgefmt: disable-next-item
r := add(xor(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)),
0xf8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff)), iszero(x))
}
}
/// @dev Find first set.
/// Returns the index of the least significant bit of `x`,
/// counting from the least significant bit position.
/// If `x` is zero, returns 256.
/// Equivalent to `ctz` (count trailing zeros), which gives
/// the number of zeros following the least significant one bit.
function ffs(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
// Isolate the least significant bit.
x := and(x, add(not(x), 1))
// For the upper 3 bits of the result, use a De Bruijn-like lookup.
// Credit to adhusson: https://blog.adhusson.com/cheap-find-first-set-evm/
// forgefmt: disable-next-item
r := shl(5, shr(252, shl(shl(2, shr(250, mul(x,
0xb6db6db6ddddddddd34d34d349249249210842108c6318c639ce739cffffffff))),
0x8040405543005266443200005020610674053026020000107506200176117077)))
// For the lower 5 bits of the result, use a De Bruijn lookup.
// forgefmt: disable-next-item
r := or(r, byte(and(div(0xd76453e0, shr(r, x)), 0x1f),
0x001f0d1e100c1d070f090b19131c1706010e11080a1a141802121b1503160405))
}
}
/// @dev Returns the number of set bits in `x`.
function popCount(uint256 x) internal pure returns (uint256 c) {
/// @solidity memory-safe-assembly
assembly {
let max := not(0)
let isMax := eq(x, max)
x := sub(x, and(shr(1, x), div(max, 3)))
x := add(and(x, div(max, 5)), and(shr(2, x), div(max, 5)))
x := and(add(x, shr(4, x)), div(max, 17))
c := or(shl(8, isMax), shr(248, mul(x, div(max, 255))))
}
}
/// @dev Returns whether `x` is a power of 2.
function isPo2(uint256 x) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `x && !(x & (x - 1))`.
result := iszero(add(and(x, sub(x, 1)), iszero(x)))
}
}
/// @dev Returns `x` reversed at the bit level.
function reverseBits(uint256 x) internal pure returns (uint256 r) {
uint256 m0 = 0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f;
uint256 m1 = m0 ^ (m0 << 2);
uint256 m2 = m1 ^ (m1 << 1);
r = reverseBytes(x);
r = (m2 & (r >> 1)) | ((m2 & r) << 1);
r = (m1 & (r >> 2)) | ((m1 & r) << 2);
r = (m0 & (r >> 4)) | ((m0 & r) << 4);
}
/// @dev Returns `x` reversed at the byte level.
function reverseBytes(uint256 x) internal pure returns (uint256 r) {
unchecked {
// Computing masks on-the-fly reduces bytecode size by about 200 bytes.
uint256 m0 = 0x100000000000000000000000000000001 * (~toUint(x == uint256(0)) >> 192);
uint256 m1 = m0 ^ (m0 << 32);
uint256 m2 = m1 ^ (m1 << 16);
uint256 m3 = m2 ^ (m2 << 8);
r = (m3 & (x >> 8)) | ((m3 & x) << 8);
r = (m2 & (r >> 16)) | ((m2 & r) << 16);
r = (m1 & (r >> 32)) | ((m1 & r) << 32);
r = (m0 & (r >> 64)) | ((m0 & r) << 64);
r = (r >> 128) | (r << 128);
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* BOOLEAN OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// A Solidity bool on the stack or memory is represented as a 256-bit word.
// Non-zero values are true, zero is false.
// A clean bool is either 0 (false) or 1 (true) under the hood.
// Usually, if not always, the bool result of a regular Solidity expression,
// or the argument of a public/external function will be a clean bool.
// You can usually use the raw variants for more performance.
// If uncertain, test (best with exact compiler settings).
// Or use the non-raw variants (compiler can sometimes optimize out the double `iszero`s).
/// @dev Returns `x & y`. Inputs must be clean.
function rawAnd(bool x, bool y) internal pure returns (bool z) {
/// @solidity memory-safe-assembly
assembly {
z := and(x, y)
}
}
/// @dev Returns `x & y`.
function and(bool x, bool y) internal pure returns (bool z) {
/// @solidity memory-safe-assembly
assembly {
z := and(iszero(iszero(x)), iszero(iszero(y)))
}
}
/// @dev Returns `x | y`. Inputs must be clean.
function rawOr(bool x, bool y) internal pure returns (bool z) {
/// @solidity memory-safe-assembly
assembly {
z := or(x, y)
}
}
/// @dev Returns `x | y`.
function or(bool x, bool y) internal pure returns (bool z) {
/// @solidity memory-safe-assembly
assembly {
z := or(iszero(iszero(x)), iszero(iszero(y)))
}
}
/// @dev Returns 1 if `b` is true, else 0. Input must be clean.
function rawToUint(bool b) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := b
}
}
/// @dev Returns 1 if `b` is true, else 0.
function toUint(bool b) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := iszero(iszero(b))
}
}
}
solady/src/utils/LibBitmap.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {LibBit} from "./LibBit.sol";
/// @notice Library for storage of packed unsigned booleans.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibBitmap.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibBitmap.sol)
/// @author Modified from Solidity-Bits (https://github.com/estarriolvetch/solidity-bits/blob/main/contracts/BitMaps.sol)
library LibBitmap {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The constant returned when a bitmap scan does not find a result.
uint256 internal constant NOT_FOUND = type(uint256).max;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STRUCTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev A bitmap in storage.
struct Bitmap {
mapping(uint256 => uint256) map;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the boolean value of the bit at `index` in `bitmap`.
function get(Bitmap storage bitmap, uint256 index) internal view returns (bool isSet) {
// It is better to set `isSet` to either 0 or 1, than zero vs non-zero.
// Both cost the same amount of gas, but the former allows the returned value
// to be reused without cleaning the upper bits.
uint256 b = (bitmap.map[index >> 8] >> (index & 0xff)) & 1;
/// @solidity memory-safe-assembly
assembly {
isSet := b
}
}
/// @dev Updates the bit at `index` in `bitmap` to true.
function set(Bitmap storage bitmap, uint256 index) internal {
bitmap.map[index >> 8] |= (1 << (index & 0xff));
}
/// @dev Updates the bit at `index` in `bitmap` to false.
function unset(Bitmap storage bitmap, uint256 index) internal {
bitmap.map[index >> 8] &= ~(1 << (index & 0xff));
}
/// @dev Flips the bit at `index` in `bitmap`.
/// Returns the boolean result of the flipped bit.
function toggle(Bitmap storage bitmap, uint256 index) internal returns (bool newIsSet) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, bitmap.slot)
mstore(0x00, shr(8, index))
let storageSlot := keccak256(0x00, 0x40)
let shift := and(index, 0xff)
let storageValue := xor(sload(storageSlot), shl(shift, 1))
// It makes sense to return the `newIsSet`,
// as it allow us to skip an additional warm `sload`,
// and it costs minimal gas (about 15),
// which may be optimized away if the returned value is unused.
newIsSet := and(1, shr(shift, storageValue))
sstore(storageSlot, storageValue)
}
}
/// @dev Updates the bit at `index` in `bitmap` to `shouldSet`.
function setTo(Bitmap storage bitmap, uint256 index, bool shouldSet) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, bitmap.slot)
mstore(0x00, shr(8, index))
let storageSlot := keccak256(0x00, 0x40)
let storageValue := sload(storageSlot)
let shift := and(index, 0xff)
sstore(
storageSlot,
// Unsets the bit at `shift` via `and`, then sets its new value via `or`.
or(and(storageValue, not(shl(shift, 1))), shl(shift, iszero(iszero(shouldSet))))
)
}
}
/// @dev Consecutively sets `amount` of bits starting from the bit at `start`.
function setBatch(Bitmap storage bitmap, uint256 start, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
let max := not(0)
let shift := and(start, 0xff)
mstore(0x20, bitmap.slot)
mstore(0x00, shr(8, start))
if iszero(lt(add(shift, amount), 257)) {
let storageSlot := keccak256(0x00, 0x40)
sstore(storageSlot, or(sload(storageSlot), shl(shift, max)))
let bucket := add(mload(0x00), 1)
let bucketEnd := add(mload(0x00), shr(8, add(amount, shift)))
amount := and(add(amount, shift), 0xff)
shift := 0
for {} iszero(eq(bucket, bucketEnd)) { bucket := add(bucket, 1) } {
mstore(0x00, bucket)
sstore(keccak256(0x00, 0x40), max)
}
mstore(0x00, bucket)
}
let storageSlot := keccak256(0x00, 0x40)
sstore(storageSlot, or(sload(storageSlot), shl(shift, shr(sub(256, amount), max))))
}
}
/// @dev Consecutively unsets `amount` of bits starting from the bit at `start`.
function unsetBatch(Bitmap storage bitmap, uint256 start, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
let shift := and(start, 0xff)
mstore(0x20, bitmap.slot)
mstore(0x00, shr(8, start))
if iszero(lt(add(shift, amount), 257)) {
let storageSlot := keccak256(0x00, 0x40)
sstore(storageSlot, and(sload(storageSlot), not(shl(shift, not(0)))))
let bucket := add(mload(0x00), 1)
let bucketEnd := add(mload(0x00), shr(8, add(amount, shift)))
amount := and(add(amount, shift), 0xff)
shift := 0
for {} iszero(eq(bucket, bucketEnd)) { bucket := add(bucket, 1) } {
mstore(0x00, bucket)
sstore(keccak256(0x00, 0x40), 0)
}
mstore(0x00, bucket)
}
let storageSlot := keccak256(0x00, 0x40)
sstore(
storageSlot, and(sload(storageSlot), not(shl(shift, shr(sub(256, amount), not(0)))))
)
}
}
/// @dev Returns number of set bits within a range by
/// scanning `amount` of bits starting from the bit at `start`.
function popCount(Bitmap storage bitmap, uint256 start, uint256 amount)
internal
view
returns (uint256 count)
{
unchecked {
uint256 bucket = start >> 8;
uint256 shift = start & 0xff;
if (!(amount + shift < 257)) {
count = LibBit.popCount(bitmap.map[bucket] >> shift);
uint256 bucketEnd = bucket + ((amount + shift) >> 8);
amount = (amount + shift) & 0xff;
shift = 0;
for (++bucket; bucket != bucketEnd; ++bucket) {
count += LibBit.popCount(bitmap.map[bucket]);
}
}
count += LibBit.popCount((bitmap.map[bucket] >> shift) << (256 - amount));
}
}
/// @dev Returns the index of the most significant set bit in `[0..upTo]`.
/// If no set bit is found, returns `NOT_FOUND`.
function findLastSet(Bitmap storage bitmap, uint256 upTo)
internal
view
returns (uint256 setBitIndex)
{
setBitIndex = NOT_FOUND;
uint256 bucket = upTo >> 8;
uint256 bits;
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, bucket)
mstore(0x20, bitmap.slot)
let offset := and(0xff, not(upTo)) // `256 - (255 & upTo) - 1`.
bits := shr(offset, shl(offset, sload(keccak256(0x00, 0x40))))
if iszero(or(bits, iszero(bucket))) {
for {} 1 {} {
bucket := add(bucket, setBitIndex) // `sub(bucket, 1)`.
mstore(0x00, bucket)
bits := sload(keccak256(0x00, 0x40))
if or(bits, iszero(bucket)) { break }
}
}
}
if (bits != 0) {
setBitIndex = (bucket << 8) | LibBit.fls(bits);
/// @solidity memory-safe-assembly
assembly {
setBitIndex := or(setBitIndex, sub(0, gt(setBitIndex, upTo)))
}
}
}
/// @dev Returns the index of the least significant unset bit in `[begin..upTo]`.
/// If no unset bit is found, returns `NOT_FOUND`.
function findFirstUnset(Bitmap storage bitmap, uint256 begin, uint256 upTo)
internal
view
returns (uint256 unsetBitIndex)
{
unsetBitIndex = NOT_FOUND;
uint256 bucket = begin >> 8;
uint256 negBits;
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, bucket)
mstore(0x20, bitmap.slot)
let offset := and(0xff, begin)
negBits := shl(offset, shr(offset, not(sload(keccak256(0x00, 0x40)))))
if iszero(negBits) {
let lastBucket := shr(8, upTo)
for {} 1 {} {
bucket := add(bucket, 1)
mstore(0x00, bucket)
negBits := not(sload(keccak256(0x00, 0x40)))
if or(negBits, gt(bucket, lastBucket)) { break }
}
if gt(bucket, lastBucket) {
negBits := shl(and(0xff, not(upTo)), shr(and(0xff, not(upTo)), negBits))
}
}
}
if (negBits != 0) {
uint256 r = (bucket << 8) | LibBit.ffs(negBits);
/// @solidity memory-safe-assembly
assembly {
unsetBitIndex := or(r, sub(0, or(gt(r, upTo), lt(r, begin))))
}
}
}
}
solady/src/utils/LibPRNG.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Library for generating pseudorandom numbers.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibPRNG.sol)
/// @author LazyShuffler based on NextShuffler by aschlosberg (divergencearran)
/// (https://github.com/divergencetech/ethier/blob/main/contracts/random/NextShuffler.sol)
library LibPRNG {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The initial length must be greater than zero and less than `2**32 - 1`.
error InvalidInitialLazyShufflerLength();
/// @dev The new length must not be less than the current length.
error InvalidNewLazyShufflerLength();
/// @dev The lazy shuffler has not been initialized.
error LazyShufflerNotInitialized();
/// @dev Cannot double initialize the lazy shuffler.
error LazyShufflerAlreadyInitialized();
/// @dev The lazy shuffle has finished.
error LazyShuffleFinished();
/// @dev The queried index is out of bounds.
error LazyShufflerGetOutOfBounds();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The scalar of ETH and most ERC20s.
uint256 internal constant WAD = 1e18;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STRUCTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev A pseudorandom number state in memory.
struct PRNG {
uint256 state;
}
/// @dev A lazy Fisher-Yates shuffler for a range `[0..n)` in storage.
struct LazyShuffler {
// Bits Layout:
// - [0..31] `numShuffled`
// - [32..223] `permutationSlot`
// - [224..255] `length`
uint256 _state;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Seeds the `prng` with `state`.
function seed(PRNG memory prng, uint256 state) internal pure {
/// @solidity memory-safe-assembly
assembly {
mstore(prng, state)
}
}
/// @dev Returns the next pseudorandom uint256.
/// All bits of the returned uint256 pass the NIST Statistical Test Suite.
function next(PRNG memory prng) internal pure returns (uint256 result) {
// We simply use `keccak256` for a great balance between
// runtime gas costs, bytecode size, and statistical properties.
//
// A high-quality LCG with a 32-byte state
// is only about 30% more gas efficient during runtime,
// but requires a 32-byte multiplier, which can cause bytecode bloat
// when this function is inlined.
//
// Using this method is about 2x more efficient than
// `nextRandomness = uint256(keccak256(abi.encode(randomness)))`.
/// @solidity memory-safe-assembly
assembly {
result := keccak256(prng, 0x20)
mstore(prng, result)
}
}
/// @dev Returns a pseudorandom uint256, uniformly distributed
/// between 0 (inclusive) and `upper` (exclusive).
/// If your modulus is big, this method is recommended
/// for uniform sampling to avoid modulo bias.
/// For uniform sampling across all uint256 values,
/// or for small enough moduli such that the bias is negligible,
/// use {next} instead.
function uniform(PRNG memory prng, uint256 upper) internal pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
for {} 1 {} {
result := keccak256(prng, 0x20)
mstore(prng, result)
if iszero(lt(result, mod(sub(0, upper), upper))) { break }
}
result := mod(result, upper)
}
}
/// @dev Returns a sample from the standard normal distribution denominated in `WAD`.
function standardNormalWad(PRNG memory prng) internal pure returns (int256 result) {
/// @solidity memory-safe-assembly
assembly {
// Technically, this is the Irwin-Hall distribution with 20 samples.
// The chance of drawing a sample outside 10 σ from the standard normal distribution
// is ≈ 0.000000000000000000000015, which is insignificant for most practical purposes.
// Passes the Kolmogorov-Smirnov test for 200k samples. Uses about 322 gas.
result := keccak256(prng, 0x20)
mstore(prng, result)
let n := 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff43 // Prime.
let a := 0x100000000000000000000000000000051 // Prime and a primitive root of `n`.
let m := 0x1fffffffffffffff1fffffffffffffff1fffffffffffffff1fffffffffffffff
let s := 0x1000000000000000100000000000000010000000000000001
let r1 := mulmod(result, a, n)
let r2 := mulmod(r1, a, n)
let r3 := mulmod(r2, a, n)
// forgefmt: disable-next-item
result := sub(sar(96, mul(26614938895861601847173011183,
add(add(shr(192, mul(s, add(and(m, result), and(m, r1)))),
shr(192, mul(s, add(and(m, r2), and(m, r3))))),
shr(192, mul(s, and(m, mulmod(r3, a, n))))))), 7745966692414833770)
}
}
/// @dev Returns a sample from the unit exponential distribution denominated in `WAD`.
function exponentialWad(PRNG memory prng) internal pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
// Passes the Kolmogorov-Smirnov test for 200k samples.
// Gas usage varies, starting from about 172+ gas.
let r := keccak256(prng, 0x20)
mstore(prng, r)
let p := shl(129, r)
let w := shl(1, r)
if iszero(gt(w, p)) {
let n := 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff43 // Prime.
let a := 0x100000000000000000000000000000051 // Prime and a primitive root of `n`.
for {} 1 {} {
r := mulmod(r, a, n)
if iszero(lt(shl(129, r), w)) {
r := mulmod(r, a, n)
result := add(1000000000000000000, result)
w := shl(1, r)
p := shl(129, r)
if iszero(lt(w, p)) { break }
continue
}
w := shl(1, r)
if iszero(lt(w, shl(129, r))) { break }
}
}
result := add(div(p, shl(129, 170141183460469231732)), result)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MEMORY ARRAY SHUFFLING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Shuffles the array in-place with Fisher-Yates shuffle.
function shuffle(PRNG memory prng, uint256[] memory a) internal pure {
/// @solidity memory-safe-assembly
assembly {
let n := mload(a)
let w := not(0)
let mask := shr(128, w)
if n {
for { a := add(a, 0x20) } 1 {} {
// We can just directly use `keccak256`, cuz
// the other approaches don't save much.
let r := keccak256(prng, 0x20)
mstore(prng, r)
// Note that there will be a very tiny modulo bias
// if the length of the array is not a power of 2.
// For all practical purposes, it is negligible
// and will not be a fairness or security concern.
{
let j := add(a, shl(5, mod(shr(128, r), n)))
n := add(n, w) // `sub(n, 1)`.
if iszero(n) { break }
let i := add(a, shl(5, n))
let t := mload(i)
mstore(i, mload(j))
mstore(j, t)
}
{
let j := add(a, shl(5, mod(and(r, mask), n)))
n := add(n, w) // `sub(n, 1)`.
if iszero(n) { break }
let i := add(a, shl(5, n))
let t := mload(i)
mstore(i, mload(j))
mstore(j, t)
}
}
}
}
}
/// @dev Shuffles the array in-place with Fisher-Yates shuffle.
function shuffle(PRNG memory prng, int256[] memory a) internal pure {
shuffle(prng, _toUints(a));
}
/// @dev Shuffles the array in-place with Fisher-Yates shuffle.
function shuffle(PRNG memory prng, address[] memory a) internal pure {
shuffle(prng, _toUints(a));
}
/// @dev Partially shuffles the array in-place with Fisher-Yates shuffle.
/// The first `k` elements will be uniformly sampled without replacement.
function shuffle(PRNG memory prng, uint256[] memory a, uint256 k) internal pure {
/// @solidity memory-safe-assembly
assembly {
let n := mload(a)
k := xor(k, mul(xor(k, n), lt(n, k))) // `min(n, k)`.
if k {
let mask := shr(128, not(0))
let b := 0
for { a := add(a, 0x20) } 1 {} {
// We can just directly use `keccak256`, cuz
// the other approaches don't save much.
let r := keccak256(prng, 0x20)
mstore(prng, r)
// Note that there will be a very tiny modulo bias
// if the length of the array is not a power of 2.
// For all practical purposes, it is negligible
// and will not be a fairness or security concern.
{
let j := add(a, shl(5, add(b, mod(shr(128, r), sub(n, b)))))
let i := add(a, shl(5, b))
let t := mload(i)
mstore(i, mload(j))
mstore(j, t)
b := add(b, 1)
if eq(b, k) { break }
}
{
let j := add(a, shl(5, add(b, mod(and(r, mask), sub(n, b)))))
let i := add(a, shl(5, b))
let t := mload(i)
mstore(i, mload(j))
mstore(j, t)
b := add(b, 1)
if eq(b, k) { break }
}
}
}
}
}
/// @dev Partially shuffles the array in-place with Fisher-Yates shuffle.
/// The first `k` elements will be uniformly sampled without replacement.
function shuffle(PRNG memory prng, int256[] memory a, uint256 k) internal pure {
shuffle(prng, _toUints(a), k);
}
/// @dev Partially shuffles the array in-place with Fisher-Yates shuffle.
/// The first `k` elements will be uniformly sampled without replacement.
function shuffle(PRNG memory prng, address[] memory a, uint256 k) internal pure {
shuffle(prng, _toUints(a), k);
}
/// @dev Shuffles the bytes in-place with Fisher-Yates shuffle.
function shuffle(PRNG memory prng, bytes memory a) internal pure {
/// @solidity memory-safe-assembly
assembly {
let n := mload(a)
let w := not(0)
let mask := shr(128, w)
if n {
let b := add(a, 0x01)
for { a := add(a, 0x20) } 1 {} {
// We can just directly use `keccak256`, cuz
// the other approaches don't save much.
let r := keccak256(prng, 0x20)
mstore(prng, r)
// Note that there will be a very tiny modulo bias
// if the length of the array is not a power of 2.
// For all practical purposes, it is negligible
// and will not be a fairness or security concern.
{
let o := mod(shr(128, r), n)
n := add(n, w) // `sub(n, 1)`.
if iszero(n) { break }
let t := mload(add(b, n))
mstore8(add(a, n), mload(add(b, o)))
mstore8(add(a, o), t)
}
{
let o := mod(and(r, mask), n)
n := add(n, w) // `sub(n, 1)`.
if iszero(n) { break }
let t := mload(add(b, n))
mstore8(add(a, n), mload(add(b, o)))
mstore8(add(a, o), t)
}
}
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE-BASED RANGE LAZY SHUFFLING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Initializes the state for lazy-shuffling the range `[0..n)`.
/// Reverts if `n == 0 || n >= 2**32 - 1`.
/// Reverts if `$` has already been initialized.
/// If you need to reduce the length after initialization, just use a fresh new `$`.
function initialize(LazyShuffler storage $, uint256 n) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(lt(sub(n, 1), 0xfffffffe)) {
mstore(0x00, 0x83b53941) // `InvalidInitialLazyShufflerLength()`.
revert(0x1c, 0x04)
}
if sload($.slot) {
mstore(0x00, 0x0c9f11f2) // `LazyShufflerAlreadyInitialized()`.
revert(0x1c, 0x04)
}
mstore(0x00, $.slot)
sstore($.slot, or(shl(224, n), shl(32, shr(64, keccak256(0x00, 0x20)))))
}
}
/// @dev Increases the length of `$`.
/// Reverts if `$` has not been initialized.
function grow(LazyShuffler storage $, uint256 n) internal {
/// @solidity memory-safe-assembly
assembly {
let state := sload($.slot) // The packed value at `$`.
// If the new length is smaller than the old length, revert.
if lt(n, shr(224, state)) {
mstore(0x00, 0xbed37c6e) // `InvalidNewLazyShufflerLength()`.
revert(0x1c, 0x04)
}
if iszero(state) {
mstore(0x00, 0x1ead2566) // `LazyShufflerNotInitialized()`.
revert(0x1c, 0x04)
}
sstore($.slot, or(shl(224, n), shr(32, shl(32, state))))
}
}
/// @dev Restarts the shuffler by setting `numShuffled` to zero,
/// such that all elements can be drawn again.
/// Restarting does NOT clear the internal permutation, nor changes the length.
/// Even with the same sequence of randomness, reshuffling can yield different results.
function restart(LazyShuffler storage $) internal {
/// @solidity memory-safe-assembly
assembly {
let state := sload($.slot)
if iszero(state) {
mstore(0x00, 0x1ead2566) // `LazyShufflerNotInitialized()`.
revert(0x1c, 0x04)
}
sstore($.slot, shl(32, shr(32, state)))
}
}
/// @dev Returns the number of elements that have been shuffled.
function numShuffled(LazyShuffler storage $) internal view returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
result := and(0xffffffff, sload($.slot))
}
}
/// @dev Returns the length of `$`.
/// Returns zero if `$` is not initialized, else a non-zero value less than `2**32 - 1`.
function length(LazyShuffler storage $) internal view returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
result := shr(224, sload($.slot))
}
}
/// @dev Returns if `$` has been initialized.
function initialized(LazyShuffler storage $) internal view returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := iszero(iszero(sload($.slot)))
}
}
/// @dev Returns if there are any more elements left to shuffle.
/// Reverts if `$` is not initialized.
function finished(LazyShuffler storage $) internal view returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
let state := sload($.slot) // The packed value at `$`.
if iszero(state) {
mstore(0x00, 0x1ead2566) // `LazyShufflerNotInitialized()`.
revert(0x1c, 0x04)
}
result := eq(shr(224, state), and(0xffffffff, state))
}
}
/// @dev Returns the current value stored at `index`, accounting for all historical shuffling.
/// Reverts if `index` is greater than or equal to the `length` of `$`.
function get(LazyShuffler storage $, uint256 index) internal view returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
let state := sload($.slot) // The packed value at `$`.
let n := shr(224, state) // Length of `$`.
if iszero(lt(index, n)) {
mstore(0x00, 0x61367cc4) // `LazyShufflerGetOutOfBounds()`.
revert(0x1c, 0x04)
}
let u32 := gt(n, 0xfffe)
let s := add(shr(sub(4, u32), index), shr(64, shl(32, state))) // Bucket slot.
let o := shl(add(4, u32), and(index, shr(u32, 15))) // Bucket slot offset (bits).
let m := sub(shl(shl(u32, 16), 1), 1) // Value mask.
result := and(m, shr(o, sload(s)))
result := xor(index, mul(xor(index, sub(result, 1)), iszero(iszero(result))))
}
}
/// @dev Does a single Fisher-Yates shuffle step, increments the `numShuffled` in `$`,
/// and returns the next value in the shuffled range.
/// `randomness` can be taken from a good-enough source, or a higher quality source like VRF.
/// Reverts if there are no more values to shuffle, which includes the case if `$` is not initialized.
function next(LazyShuffler storage $, uint256 randomness) internal returns (uint256 chosen) {
/// @solidity memory-safe-assembly
assembly {
function _get(u32_, state_, i_) -> _value {
let s_ := add(shr(sub(4, u32_), i_), shr(64, shl(32, state_))) // Bucket slot.
let o_ := shl(add(4, u32_), and(i_, shr(u32_, 15))) // Bucket slot offset (bits).
let m_ := sub(shl(shl(u32_, 16), 1), 1) // Value mask.
_value := and(m_, shr(o_, sload(s_)))
_value := xor(i_, mul(xor(i_, sub(_value, 1)), iszero(iszero(_value))))
}
function _set(u32_, state_, i_, value_) {
let s_ := add(shr(sub(4, u32_), i_), shr(64, shl(32, state_))) // Bucket slot.
let o_ := shl(add(4, u32_), and(i_, shr(u32_, 15))) // Bucket slot offset (bits).
let m_ := sub(shl(shl(u32_, 16), 1), 1) // Value mask.
let v_ := sload(s_) // Bucket slot value.
value_ := mul(iszero(eq(i_, value_)), add(value_, 1))
sstore(s_, xor(v_, shl(o_, and(m_, xor(shr(o_, v_), value_)))))
}
let state := sload($.slot) // The packed value at `$`.
let shuffled := and(0xffffffff, state) // Number of elements shuffled.
let n := shr(224, state) // Length of `$`.
let remainder := sub(n, shuffled) // Number of elements left to shuffle.
if iszero(remainder) {
mstore(0x00, 0x51065f79) // `LazyShuffleFinished()`.
revert(0x1c, 0x04)
}
mstore(0x00, randomness) // (Re)hash the randomness so that we don't
mstore(0x20, shuffled) // need to expect guarantees on its distribution.
let index := add(mod(keccak256(0x00, 0x40), remainder), shuffled)
chosen := _get(gt(n, 0xfffe), state, index)
_set(gt(n, 0xfffe), state, index, _get(gt(n, 0xfffe), state, shuffled))
_set(gt(n, 0xfffe), state, shuffled, chosen)
sstore($.slot, add(1, state)) // Increment the `numShuffled` by 1, and store it.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PRIVATE HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Reinterpret cast to an uint256 array.
function _toUints(int256[] memory a) private pure returns (uint256[] memory casted) {
/// @solidity memory-safe-assembly
assembly {
casted := a
}
}
/// @dev Reinterpret cast to an uint256 array.
function _toUints(address[] memory a) private pure returns (uint256[] memory casted) {
/// @solidity memory-safe-assembly
assembly {
// As any address written to memory will have the upper 96 bits
// of the word zeroized (as per Solidity spec), we can directly
// compare these addresses as if they are whole uint256 words.
casted := a
}
}
}
solady/src/utils/SSTORE2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Read and write to persistent storage at a fraction of the cost.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SSTORE2.sol)
/// @author Saw-mon-and-Natalie (https://github.com/Saw-mon-and-Natalie)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol)
/// @author Modified from 0xSequence (https://github.com/0xSequence/sstore2/blob/master/contracts/SSTORE2.sol)
/// @author Modified from SSTORE3 (https://github.com/Philogy/sstore3)
library SSTORE2 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The proxy initialization code.
uint256 private constant _CREATE3_PROXY_INITCODE = 0x67363d3d37363d34f03d5260086018f3;
/// @dev Hash of the `_CREATE3_PROXY_INITCODE`.
/// Equivalent to `keccak256(abi.encodePacked(hex"67363d3d37363d34f03d5260086018f3"))`.
bytes32 internal constant CREATE3_PROXY_INITCODE_HASH =
0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Unable to deploy the storage contract.
error DeploymentFailed();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* WRITE LOGIC */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Writes `data` into the bytecode of a storage contract and returns its address.
function write(bytes memory data) internal returns (address pointer) {
/// @solidity memory-safe-assembly
assembly {
let n := mload(data) // Let `l` be `n + 1`. +1 as we prefix a STOP opcode.
/**
* ---------------------------------------------------+
* Opcode | Mnemonic | Stack | Memory |
* ---------------------------------------------------|
* 61 l | PUSH2 l | l | |
* 80 | DUP1 | l l | |
* 60 0xa | PUSH1 0xa | 0xa l l | |
* 3D | RETURNDATASIZE | 0 0xa l l | |
* 39 | CODECOPY | l | [0..l): code |
* 3D | RETURNDATASIZE | 0 l | [0..l): code |
* F3 | RETURN | | [0..l): code |
* 00 | STOP | | |
* ---------------------------------------------------+
* @dev Prefix the bytecode with a STOP opcode to ensure it cannot be called.
* Also PUSH2 is used since max contract size cap is 24,576 bytes which is less than 2 ** 16.
*/
// Do a out-of-gas revert if `n + 1` is more than 2 bytes.
mstore(add(data, gt(n, 0xfffe)), add(0xfe61000180600a3d393df300, shl(0x40, n)))
// Deploy a new contract with the generated creation code.
pointer := create(0, add(data, 0x15), add(n, 0xb))
if iszero(pointer) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(data, n) // Restore the length of `data`.
}
}
/// @dev Writes `data` into the bytecode of a storage contract with `salt`
/// and returns its normal CREATE2 deterministic address.
function writeCounterfactual(bytes memory data, bytes32 salt)
internal
returns (address pointer)
{
/// @solidity memory-safe-assembly
assembly {
let n := mload(data)
// Do a out-of-gas revert if `n + 1` is more than 2 bytes.
mstore(add(data, gt(n, 0xfffe)), add(0xfe61000180600a3d393df300, shl(0x40, n)))
// Deploy a new contract with the generated creation code.
pointer := create2(0, add(data, 0x15), add(n, 0xb), salt)
if iszero(pointer) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(data, n) // Restore the length of `data`.
}
}
/// @dev Writes `data` into the bytecode of a storage contract and returns its address.
/// This uses the so-called "CREATE3" workflow,
/// which means that `pointer` is agnostic to `data, and only depends on `salt`.
function writeDeterministic(bytes memory data, bytes32 salt)
internal
returns (address pointer)
{
/// @solidity memory-safe-assembly
assembly {
let n := mload(data)
mstore(0x00, _CREATE3_PROXY_INITCODE) // Store the `_PROXY_INITCODE`.
let proxy := create2(0, 0x10, 0x10, salt)
if iszero(proxy) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(0x14, proxy) // Store the proxy's address.
// 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01).
// 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex).
mstore(0x00, 0xd694)
mstore8(0x34, 0x01) // Nonce of the proxy contract (1).
pointer := keccak256(0x1e, 0x17)
// Do a out-of-gas revert if `n + 1` is more than 2 bytes.
mstore(add(data, gt(n, 0xfffe)), add(0xfe61000180600a3d393df300, shl(0x40, n)))
if iszero(
mul( // The arguments of `mul` are evaluated last to first.
extcodesize(pointer),
call(gas(), proxy, 0, add(data, 0x15), add(n, 0xb), codesize(), 0x00)
)
) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(data, n) // Restore the length of `data`.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ADDRESS CALCULATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the initialization code hash of the storage contract for `data`.
/// Used for mining vanity addresses with create2crunch.
function initCodeHash(bytes memory data) internal pure returns (bytes32 hash) {
/// @solidity memory-safe-assembly
assembly {
let n := mload(data)
// Do a out-of-gas revert if `n + 1` is more than 2 bytes.
returndatacopy(returndatasize(), returndatasize(), gt(n, 0xfffe))
mstore(data, add(0x61000180600a3d393df300, shl(0x40, n)))
hash := keccak256(add(data, 0x15), add(n, 0xb))
mstore(data, n) // Restore the length of `data`.
}
}
/// @dev Equivalent to `predictCounterfactualAddress(data, salt, address(this))`
function predictCounterfactualAddress(bytes memory data, bytes32 salt)
internal
view
returns (address pointer)
{
pointer = predictCounterfactualAddress(data, salt, address(this));
}
/// @dev Returns the CREATE2 address of the storage contract for `data`
/// deployed with `salt` by `deployer`.
/// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly.
function predictCounterfactualAddress(bytes memory data, bytes32 salt, address deployer)
internal
pure
returns (address predicted)
{
bytes32 hash = initCodeHash(data);
/// @solidity memory-safe-assembly
assembly {
// Compute and store the bytecode hash.
mstore8(0x00, 0xff) // Write the prefix.
mstore(0x35, hash)
mstore(0x01, shl(96, deployer))
mstore(0x15, salt)
predicted := keccak256(0x00, 0x55)
// Restore the part of the free memory pointer that has been overwritten.
mstore(0x35, 0)
}
}
/// @dev Equivalent to `predictDeterministicAddress(salt, address(this))`.
function predictDeterministicAddress(bytes32 salt) internal view returns (address pointer) {
pointer = predictDeterministicAddress(salt, address(this));
}
/// @dev Returns the "CREATE3" deterministic address for `salt` with `deployer`.
function predictDeterministicAddress(bytes32 salt, address deployer)
internal
pure
returns (address pointer)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x00, deployer) // Store `deployer`.
mstore8(0x0b, 0xff) // Store the prefix.
mstore(0x20, salt) // Store the salt.
mstore(0x40, CREATE3_PROXY_INITCODE_HASH) // Store the bytecode hash.
mstore(0x14, keccak256(0x0b, 0x55)) // Store the proxy's address.
mstore(0x40, m) // Restore the free memory pointer.
// 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01).
// 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex).
mstore(0x00, 0xd694)
mstore8(0x34, 0x01) // Nonce of the proxy contract (1).
pointer := keccak256(0x1e, 0x17)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* READ LOGIC */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Equivalent to `read(pointer, 0, 2 ** 256 - 1)`.
function read(address pointer) internal view returns (bytes memory data) {
/// @solidity memory-safe-assembly
assembly {
data := mload(0x40)
let n := and(sub(extcodesize(pointer), 0x01), 0xffffffffff)
extcodecopy(pointer, add(data, 0x1f), 0x00, add(n, 0x21))
mstore(data, n) // Store the length.
mstore(0x40, add(n, add(data, 0x40))) // Allocate memory.
}
}
/// @dev Equivalent to `read(pointer, start, 2 ** 256 - 1)`.
function read(address pointer, uint256 start) internal view returns (bytes memory data) {
/// @solidity memory-safe-assembly
assembly {
data := mload(0x40)
let n := and(sub(extcodesize(pointer), 0x01), 0xffffffffff)
extcodecopy(pointer, add(data, 0x1f), start, add(n, 0x21))
mstore(data, mul(sub(n, start), lt(start, n))) // Store the length.
mstore(0x40, add(data, add(0x40, mload(data)))) // Allocate memory.
}
}
/// @dev Returns the a slice of the data on `pointer` from `start` to `end`.
/// `start` and `end` will be clamped to the range `[0, args.length]`.
/// The `pointer` MUST be deployed via the SSTORE2 write functions.
/// Otherwise, the behavior is undefined.
/// Out-of-gas reverts if `pointer` does not have any code.
function read(address pointer, uint256 start, uint256 end)
internal
view
returns (bytes memory data)
{
/// @solidity memory-safe-assembly
assembly {
data := mload(0x40)
let d := and(0xffff, sub(end, start))
extcodecopy(pointer, add(data, 0x1f), start, add(d, 0x01))
if iszero(and(0xff, mload(add(data, d)))) {
let n := sub(extcodesize(pointer), 0x01)
returndatacopy(returndatasize(), returndatasize(), shr(64, n))
d := mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n))))
}
mstore(data, mul(d, lt(start, end))) // Store the length.
mstore(add(add(data, 0x20), d), 0) // Zeroize the slot after the bytes.
mstore(0x40, add(add(data, 0x40), d)) // Allocate memory.
}
}
}
solady/src/utils/SafeTransferLib.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @author Permit2 operations from (https://github.com/Uniswap/permit2/blob/main/src/libraries/Permit2Lib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
/// - For ERC20s, this implementation won't check that a token has code,
/// responsibility is delegated to the caller.
library SafeTransferLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ETH transfer has failed.
error ETHTransferFailed();
/// @dev The ERC20 `transferFrom` has failed.
error TransferFromFailed();
/// @dev The ERC20 `transfer` has failed.
error TransferFailed();
/// @dev The ERC20 `approve` has failed.
error ApproveFailed();
/// @dev The Permit2 operation has failed.
error Permit2Failed();
/// @dev The Permit2 amount must be less than `2**160 - 1`.
error Permit2AmountOverflow();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;
/// @dev Suggested gas stipend for contract receiving ETH to perform a few
/// storage reads and writes, but low enough to prevent griefing.
uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;
/// @dev The unique EIP-712 domain domain separator for the DAI token contract.
bytes32 internal constant DAI_DOMAIN_SEPARATOR =
0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7;
/// @dev The address for the WETH9 contract on Ethereum mainnet.
address internal constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
/// @dev The canonical Permit2 address.
/// [Github](https://github.com/Uniswap/permit2)
/// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)
address internal constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ETH OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
//
// The regular variants:
// - Forwards all remaining gas to the target.
// - Reverts if the target reverts.
// - Reverts if the current contract has insufficient balance.
//
// The force variants:
// - Forwards with an optional gas stipend
// (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
// - If the target reverts, or if the gas stipend is exhausted,
// creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
// Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
// - Reverts if the current contract has insufficient balance.
//
// The try variants:
// - Forwards with a mandatory gas stipend.
// - Instead of reverting, returns whether the transfer succeeded.
/// @dev Sends `amount` (in wei) ETH to `to`.
function safeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Sends all the ETH in the current contract to `to`.
function safeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// Transfer all the ETH and check if it succeeded or not.
if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// forgefmt: disable-next-item
if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
}
}
/// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
function trySafeTransferAllETH(address to, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for
/// the current contract to manage.
function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
///
/// The `from` account must have at least `amount` approved for the current contract to manage.
function trySafeTransferFrom(address token, address from, address to, uint256 amount)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
success :=
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends all of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have their entire balance approved for the current contract to manage.
function safeTransferAllFrom(address token, address from, address to)
internal
returns (uint256 amount)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransfer(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sends all of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransferAll(address token, address to) internal returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
mstore(0x20, address()) // Store the address of the current contract.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x14, to) // Store the `to` argument.
amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// Reverts upon failure.
function safeApprove(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// If the initial attempt to approve fails, attempts to reset the approved amount to zero,
/// then retries the approval again (some tokens, e.g. USDT, requires this).
/// Reverts upon failure.
function safeApproveWithRetry(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, retrying upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x34, 0) // Store 0 for the `amount`.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.
mstore(0x34, amount) // Store back the original `amount`.
// Retry the approval, reverting upon failure.
if iszero(
and(
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Returns the amount of ERC20 `token` owned by `account`.
/// Returns zero if the `token` does not exist.
function balanceOf(address token, address account) internal view returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, account) // Store the `account` argument.
mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
amount :=
mul( // The arguments of `mul` are evaluated from right to left.
mload(0x20),
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
)
)
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// If the initial attempt fails, try to use Permit2 to transfer the token.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for the current contract to manage.
function safeTransferFrom2(address token, address from, address to, uint256 amount) internal {
if (!trySafeTransferFrom(token, from, to, amount)) {
permit2TransferFrom(token, from, to, amount);
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to` via Permit2.
/// Reverts upon failure.
function permit2TransferFrom(address token, address from, address to, uint256 amount)
internal
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(add(m, 0x74), shr(96, shl(96, token)))
mstore(add(m, 0x54), amount)
mstore(add(m, 0x34), to)
mstore(add(m, 0x20), shl(96, from))
// `transferFrom(address,address,uint160,address)`.
mstore(m, 0x36c78516000000000000000000000000)
let p := PERMIT2
let exists := eq(chainid(), 1)
if iszero(exists) { exists := iszero(iszero(extcodesize(p))) }
if iszero(and(call(gas(), p, 0, add(m, 0x10), 0x84, codesize(), 0x00), exists)) {
mstore(0x00, 0x7939f4248757f0fd) // `TransferFromFailed()` or `Permit2AmountOverflow()`.
revert(add(0x18, shl(2, iszero(iszero(shr(160, amount))))), 0x04)
}
}
}
/// @dev Permit a user to spend a given amount of
/// another user's tokens via native EIP-2612 permit if possible, falling
/// back to Permit2 if native permit fails or is not implemented on the token.
function permit2(
address token,
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
for {} shl(96, xor(token, WETH9)) {} {
mstore(0x00, 0x3644e515) // `DOMAIN_SEPARATOR()`.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
lt(iszero(mload(0x00)), eq(returndatasize(), 0x20)), // Returns 1 non-zero word.
// Gas stipend to limit gas burn for tokens that don't refund gas when
// an non-existing function is called. 5K should be enough for a SLOAD.
staticcall(5000, token, 0x1c, 0x04, 0x00, 0x20)
)
) { break }
// After here, we can be sure that token is a contract.
let m := mload(0x40)
mstore(add(m, 0x34), spender)
mstore(add(m, 0x20), shl(96, owner))
mstore(add(m, 0x74), deadline)
if eq(mload(0x00), DAI_DOMAIN_SEPARATOR) {
mstore(0x14, owner)
mstore(0x00, 0x7ecebe00000000000000000000000000) // `nonces(address)`.
mstore(add(m, 0x94), staticcall(gas(), token, 0x10, 0x24, add(m, 0x54), 0x20))
mstore(m, 0x8fcbaf0c000000000000000000000000) // `IDAIPermit.permit`.
// `nonces` is already at `add(m, 0x54)`.
// `1` is already stored at `add(m, 0x94)`.
mstore(add(m, 0xb4), and(0xff, v))
mstore(add(m, 0xd4), r)
mstore(add(m, 0xf4), s)
success := call(gas(), token, 0, add(m, 0x10), 0x104, codesize(), 0x00)
break
}
mstore(m, 0xd505accf000000000000000000000000) // `IERC20Permit.permit`.
mstore(add(m, 0x54), amount)
mstore(add(m, 0x94), and(0xff, v))
mstore(add(m, 0xb4), r)
mstore(add(m, 0xd4), s)
success := call(gas(), token, 0, add(m, 0x10), 0xe4, codesize(), 0x00)
break
}
}
if (!success) simplePermit2(token, owner, spender, amount, deadline, v, r, s);
}
/// @dev Simple permit on the Permit2 contract.
function simplePermit2(
address token,
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, 0x927da105) // `allowance(address,address,address)`.
{
let addressMask := shr(96, not(0))
mstore(add(m, 0x20), and(addressMask, owner))
mstore(add(m, 0x40), and(addressMask, token))
mstore(add(m, 0x60), and(addressMask, spender))
mstore(add(m, 0xc0), and(addressMask, spender))
}
let p := mul(PERMIT2, iszero(shr(160, amount)))
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x5f), // Returns 3 words: `amount`, `expiration`, `nonce`.
staticcall(gas(), p, add(m, 0x1c), 0x64, add(m, 0x60), 0x60)
)
) {
mstore(0x00, 0x6b836e6b8757f0fd) // `Permit2Failed()` or `Permit2AmountOverflow()`.
revert(add(0x18, shl(2, iszero(p))), 0x04)
}
mstore(m, 0x2b67b570) // `Permit2.permit` (PermitSingle variant).
// `owner` is already `add(m, 0x20)`.
// `token` is already at `add(m, 0x40)`.
mstore(add(m, 0x60), amount)
mstore(add(m, 0x80), 0xffffffffffff) // `expiration = type(uint48).max`.
// `nonce` is already at `add(m, 0xa0)`.
// `spender` is already at `add(m, 0xc0)`.
mstore(add(m, 0xe0), deadline)
mstore(add(m, 0x100), 0x100) // `signature` offset.
mstore(add(m, 0x120), 0x41) // `signature` length.
mstore(add(m, 0x140), r)
mstore(add(m, 0x160), s)
mstore(add(m, 0x180), shl(248, v))
if iszero(call(gas(), p, 0, add(m, 0x1c), 0x184, codesize(), 0x00)) {
mstore(0x00, 0x6b836e6b) // `Permit2Failed()`.
revert(0x1c, 0x04)
}
}
}
}
Compiler Settings
{"viaIR":true,"outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode","evm.methodIdentifiers","metadata"],"":["ast"]}},"optimizer":{"runs":1000,"enabled":true},"libraries":{},"evmVersion":"cancun"}
Contract ABI
[{"type":"constructor","stateMutability":"payable","inputs":[{"type":"address","name":"_rand","internalType":"address"}]},{"type":"error","name":"IndexOutOfBounds","inputs":[]},{"type":"error","name":"Misconfigured","inputs":[]},{"type":"error","name":"SignerMismatch","inputs":[]},{"type":"event","name":"Ok","inputs":[{"type":"address","name":"provider","internalType":"address","indexed":true},{"type":"bytes32","name":"section","internalType":"bytes32","indexed":false}],"anonymous":false},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"at","inputs":[{"type":"tuple","name":"info","internalType":"struct PreimageLocation.Info","components":[{"type":"address","name":"provider","internalType":"address"},{"type":"bool","name":"callAtChange","internalType":"bool"},{"type":"bool","name":"durationIsTimestamp","internalType":"bool"},{"type":"uint256","name":"duration","internalType":"uint256"},{"type":"address","name":"token","internalType":"address"},{"type":"uint256","name":"price","internalType":"uint256"},{"type":"uint256","name":"offset","internalType":"uint256"},{"type":"uint256","name":"index","internalType":"uint256"}]}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"len","internalType":"uint256"},{"type":"bytes","name":"indices","internalType":"bytes"}],"name":"consumed","inputs":[{"type":"tuple","name":"section","internalType":"struct PreimageLocation.Info","components":[{"type":"address","name":"provider","internalType":"address"},{"type":"bool","name":"callAtChange","internalType":"bool"},{"type":"bool","name":"durationIsTimestamp","internalType":"bool"},{"type":"uint256","name":"duration","internalType":"uint256"},{"type":"address","name":"token","internalType":"address"},{"type":"uint256","name":"price","internalType":"uint256"},{"type":"uint256","name":"offset","internalType":"uint256"},{"type":"uint256","name":"index","internalType":"uint256"}]}]},{"type":"function","stateMutability":"payable","outputs":[],"name":"ok","inputs":[{"type":"tuple[]","name":"infos","internalType":"struct PreimageLocation.Info[]","components":[{"type":"address","name":"provider","internalType":"address"},{"type":"bool","name":"callAtChange","internalType":"bool"},{"type":"bool","name":"durationIsTimestamp","internalType":"bool"},{"type":"uint256","name":"duration","internalType":"uint256"},{"type":"address","name":"token","internalType":"address"},{"type":"uint256","name":"price","internalType":"uint256"},{"type":"uint256","name":"offset","internalType":"uint256"},{"type":"uint256","name":"index","internalType":"uint256"}]}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes","name":"","internalType":"bytes"}],"name":"pointer","inputs":[{"type":"tuple","name":"info","internalType":"struct PreimageLocation.Info","components":[{"type":"address","name":"provider","internalType":"address"},{"type":"bool","name":"callAtChange","internalType":"bool"},{"type":"bool","name":"durationIsTimestamp","internalType":"bool"},{"type":"uint256","name":"duration","internalType":"uint256"},{"type":"address","name":"token","internalType":"address"},{"type":"uint256","name":"price","internalType":"uint256"},{"type":"uint256","name":"offset","internalType":"uint256"},{"type":"uint256","name":"index","internalType":"uint256"}]}]}]
Contract Creation Code
0x6080601f61099d38819003918201601f19168301916001600160401b03831184841017606f57808492602094604052833981010312606b57516001600160a01b03811690819003606b575f80546001600160a01b03191691909117905560405161091990816100848239f35b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806338ee1b8e146103d75780633fdad9c41461030f57806368a73e93146100865763eca4ba7014610045575f80fd5b34610082576101003660031901126100825761007e61006a610065610753565b6108b9565b60405191829160208352602083019061066a565b0390f35b5f80fd5b6020806003193601126100825767ffffffffffffffff6004358181116100825736602382011215610082578060040135918211610082576024810190602436918460081b01011161008257335f52826e2fd5aeb385d324b580fca7c83823a08033146102fd575b506dd9ecebf3c23529de49815dac1c4c8033146102e7575b50505f515f926001926001600160a01b03809316935b156102de575b5f9461012e818484610743565b358481168091036100825785036102b45761014a818484610743565b906101008236031261008257858589604051946101668661068e565b61016f816106fd565b80875261017d828401610711565b9182848901526040810161019090610711565b9788604082015260609081830135998a838301526080958685016101b3906106fd565b90818885015260a0928387013594858582015260c09788810135809983015260e001359060e0015215155f147fc000000000000000000000000000000000000000000000000000000000000000787fffffffff000000000000000000000000000000000000000060019f7f786f6c9fa380484e920dd424c96d0649b4a5abd7cee90c5c1fb6d4502abde81f9d7f8000000000000000000000000000000000000000000000000000000000000000946102ad576001955b156102a6576001965b816040519d168d5216961b169360fe1b169160ff1b1617171786850152604084015282015220604051908152a2019461011b565b5f96610272565b5f95610269565b60046040517f10c74b03000000000000000000000000000000000000000000000000000000008152fd5b81851061012157005b5f8080925afa156102f9578284610105565b3838fd5b5f8080925afa156102f95782846100ed565b346100825761010036600319011261008257610329610753565b60e435908160051b906020928083048414901517156103c357828201918281116103c3576040908151938591602181601f8801873c86860194855160ff161561039e575b50108102845283015f858201520191826040525190519083811061039057508152f35b5f1990840360031b1b168152f35b9092503b8260215f1983019283871c3d3d3e820301828411028803911102918761036d565b634e487b7160e01b5f52601160045260245ffd5b3461008257610100366003190112610082576103f4610065610753565b51600160078260051c16115f146106645760015b8160081c0161042f610419826106e1565b9161042760405193846106bf565b8083526106e1565b601f19013660208301375f906001926001600160a01b035f5416935b1561063b575b5f92610100366003190112610082576040519061046d8261068e565b6001600160a01b03600435166004350361008257600435825260243515156024350361008257602435602083015260443515156044350361008257604435604083015260643560608301526001600160a01b03608435166084350361008257608435608083015260a43560a083015260c43560c08301528060e083015260e0604051927f38ee1b8e0000000000000000000000000000000000000000000000000000000084526001600160a01b038151166004850152602081015115156024850152604081015115156044850152606081015160648501526001600160a01b03608082015116608485015260a081015160a485015260c081015160c4850152015160e483015260208261010481895afa8015610630575f906105f2575b6001925061059a575b019261044b565b8060031c6105ec7fff000000000000000000000000000000000000000000000000000000000000006105cc838861071e565b5160f89060ff87600788166007031b1690821c17901b165f1a918661071e565b53610593565b50906020813d602011610628575b8161060d602093836106bf565b8101031261008257519081151582036100825760019161058a565b3d9150610600565b6040513d5f823e3d90fd5b8060051c83106104515761007e60405192839260051c835260406020840152604083019061066a565b5f610408565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b610100810190811067ffffffffffffffff8211176106ab57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176106ab57604052565b67ffffffffffffffff81116106ab57601f01601f191660200190565b35906001600160a01b038216820361008257565b3590811515820361008257565b90815181101561072f570160200190565b634e487b7160e01b5f52603260045260245ffd5b919081101561072f5760081b0190565b6001600160a01b03805f5416604051917feca4ba70000000000000000000000000000000000000000000000000000000008352600435818116809103610082576004840152602435801515809103610082576024840152604435801515809103610082576044840152606435606484015260843581811680910361008257608484015260a43560a484015260c43560c48401526020836101048160e435958660e48301525afa928315610630575f9361087c575b5082161561085257813b60051c905f1982019182116103c357116108285790565b60046040517f4e23d035000000000000000000000000000000000000000000000000000000008152fd5b60046040517f43f3e27e000000000000000000000000000000000000000000000000000000008152fd5b9092506020813d6020116108b1575b81610898602093836106bf565b810103126100825751828116810361008257915f610807565b3d915061088b565b9060408051809364ffffffffff5f19823b0116905f6021830191601f8501903c808252010160405256fea26469706673582212204aa524019baab0f1ea588c9d3934d2e09ea1520131476ea48e208af672d011a564736f6c63430008190033000000000000000000000000bab99bdbc920ec9d0843993e98c11d7e482814b7
Deployed ByteCode
0x60806040526004361015610011575f80fd5b5f3560e01c806338ee1b8e146103d75780633fdad9c41461030f57806368a73e93146100865763eca4ba7014610045575f80fd5b34610082576101003660031901126100825761007e61006a610065610753565b6108b9565b60405191829160208352602083019061066a565b0390f35b5f80fd5b6020806003193601126100825767ffffffffffffffff6004358181116100825736602382011215610082578060040135918211610082576024810190602436918460081b01011161008257335f52826e2fd5aeb385d324b580fca7c83823a08033146102fd575b506dd9ecebf3c23529de49815dac1c4c8033146102e7575b50505f515f926001926001600160a01b03809316935b156102de575b5f9461012e818484610743565b358481168091036100825785036102b45761014a818484610743565b906101008236031261008257858589604051946101668661068e565b61016f816106fd565b80875261017d828401610711565b9182848901526040810161019090610711565b9788604082015260609081830135998a838301526080958685016101b3906106fd565b90818885015260a0928387013594858582015260c09788810135809983015260e001359060e0015215155f147fc000000000000000000000000000000000000000000000000000000000000000787fffffffff000000000000000000000000000000000000000060019f7f786f6c9fa380484e920dd424c96d0649b4a5abd7cee90c5c1fb6d4502abde81f9d7f8000000000000000000000000000000000000000000000000000000000000000946102ad576001955b156102a6576001965b816040519d168d5216961b169360fe1b169160ff1b1617171786850152604084015282015220604051908152a2019461011b565b5f96610272565b5f95610269565b60046040517f10c74b03000000000000000000000000000000000000000000000000000000008152fd5b81851061012157005b5f8080925afa156102f9578284610105565b3838fd5b5f8080925afa156102f95782846100ed565b346100825761010036600319011261008257610329610753565b60e435908160051b906020928083048414901517156103c357828201918281116103c3576040908151938591602181601f8801873c86860194855160ff161561039e575b50108102845283015f858201520191826040525190519083811061039057508152f35b5f1990840360031b1b168152f35b9092503b8260215f1983019283871c3d3d3e820301828411028803911102918761036d565b634e487b7160e01b5f52601160045260245ffd5b3461008257610100366003190112610082576103f4610065610753565b51600160078260051c16115f146106645760015b8160081c0161042f610419826106e1565b9161042760405193846106bf565b8083526106e1565b601f19013660208301375f906001926001600160a01b035f5416935b1561063b575b5f92610100366003190112610082576040519061046d8261068e565b6001600160a01b03600435166004350361008257600435825260243515156024350361008257602435602083015260443515156044350361008257604435604083015260643560608301526001600160a01b03608435166084350361008257608435608083015260a43560a083015260c43560c08301528060e083015260e0604051927f38ee1b8e0000000000000000000000000000000000000000000000000000000084526001600160a01b038151166004850152602081015115156024850152604081015115156044850152606081015160648501526001600160a01b03608082015116608485015260a081015160a485015260c081015160c4850152015160e483015260208261010481895afa8015610630575f906105f2575b6001925061059a575b019261044b565b8060031c6105ec7fff000000000000000000000000000000000000000000000000000000000000006105cc838861071e565b5160f89060ff87600788166007031b1690821c17901b165f1a918661071e565b53610593565b50906020813d602011610628575b8161060d602093836106bf565b8101031261008257519081151582036100825760019161058a565b3d9150610600565b6040513d5f823e3d90fd5b8060051c83106104515761007e60405192839260051c835260406020840152604083019061066a565b5f610408565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b610100810190811067ffffffffffffffff8211176106ab57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176106ab57604052565b67ffffffffffffffff81116106ab57601f01601f191660200190565b35906001600160a01b038216820361008257565b3590811515820361008257565b90815181101561072f570160200190565b634e487b7160e01b5f52603260045260245ffd5b919081101561072f5760081b0190565b6001600160a01b03805f5416604051917feca4ba70000000000000000000000000000000000000000000000000000000008352600435818116809103610082576004840152602435801515809103610082576024840152604435801515809103610082576044840152606435606484015260843581811680910361008257608484015260a43560a484015260c43560c48401526020836101048160e435958660e48301525afa928315610630575f9361087c575b5082161561085257813b60051c905f1982019182116103c357116108285790565b60046040517f4e23d035000000000000000000000000000000000000000000000000000000008152fd5b60046040517f43f3e27e000000000000000000000000000000000000000000000000000000008152fd5b9092506020813d6020116108b1575b81610898602093836106bf565b810103126100825751828116810361008257915f610807565b3d915061088b565b9060408051809364ffffffffff5f19823b0116905f6021830191601f8501903c808252010160405256fea26469706673582212204aa524019baab0f1ea588c9d3934d2e09ea1520131476ea48e208af672d011a564736f6c63430008190033