Skip to content

Instantly share code, notes, and snippets.

@nelsonhp
Last active March 20, 2021 23:43
Show Gist options
  • Save nelsonhp/8c7175c90b44b9abc603ae8fcfc42e32 to your computer and use it in GitHub Desktop.
Save nelsonhp/8c7175c90b44b9abc603ae8fcfc42e32 to your computer and use it in GitHub Desktop.
/*
Copyright 2021 Nelson Hunter Prendergast
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
object "AtomicProxy" {
/*
You are hereby warned:
- Using this contract is DANGEROUS.
- This code has not been reviewed.
- The entire contract is written in assembly.
- This code was written for fun, not production.
- Almost all safety has been thrown out the window to minimize gas.
What:
This contract allows a user to batch send transactions
with near optimal overhead. This contract never stores to
or loads from storage in any way ( unless done in a delegatecall )
This contract allows you to perform:
- Multiple calls with no value
- A Single call with all passed value
- Multiple delegate calls
Why:
Gas is expensive and atomic transactions are useful.
How:
Black magic.
Usage:
CallData:
Contains a list of Tx Spec objects. Each Tx Spec
describes a transaction in a length prefix encoding.
These transactions will be called in order by
iteratively parsing the next object and performing
the encoded call.
Tx Spec:
Describes a transaction to send.
CallDataSize:
Number of bytes in calldata for this tx
< uint16 >
CallTypeFlags:
Determines if this tx is a call vs delegatecall and if value should be sent with call.
See switch case for details.
< uint8 >
Target:
Where call should be sent to.
< 20 bytes >
CallData:
Actual CallData to be sent encoded as abi.encode would marshall CallData
if you where encoding it using actual types and the function signature.
< Dynamic number of bytes >
Optional Value:
If the value of CallTypeFlags is set to 3, this value will be parsed
and used to populate the value argument to a call transaction.
[< uint256 >]
Operational Details:
A failure in any transaction will revert all transactions.
Only the last transaction in list will return data to caller.
The contract is owner protected, but the owner is set in the
runtime code. As such, the owner can not be changed, but gas
is saved by not loading from memory.
*/
code {
// deploy the contract
datacopy(0, dataoffset("runtime"), datasize("runtime"))
// store caller as owner for access control checks
setimmutable("OWNER", caller())
return(0, datasize("runtime"))
}
object "runtime" {
code {
// make sure caller is owner
if iszero(eq(loadimmutable("OWNER"), caller())) {
revert(0,0)
}
// calldata read offset
let offset := 0
// size of calldata
let callDataSize := calldatasize()
// where to store result
let result := 0
// iterate loop until not enough remaining data
for {} gt(sub(callDataSize, offset), 0x17) {} {
// read data at offset
let offsetData := calldataload(offset)
// isolate target address but leave HOB - EVM will zero for us
let target := shr(72, offsetData)
// get size of call data
let callSize := shr(240, offsetData)
// get pointer to head of this tx's calldata
let dataPtr := add(0x17, offset)
// copy calldata to memory
calldatacopy(0, dataPtr, callSize)
// update offset for next iteration
offset := add(dataPtr, callSize)
// determine case and perform call
switch and(offsetData, 0x0000ff00000000000000000000000000000000000000000000000000000000000000)
case 0x00000000000000000000000000000000000000000000000000000000000000000000 { // delegatecall = false value = false
result := call(gas(), target, 0, 0, callSize, 0, 0)
}
case 0x00000100000000000000000000000000000000000000000000000000000000000000 { // delegatecall = false value = true
result := call(gas(), target, callvalue(), 0, callSize, 0, 0)
}
case 0x00000200000000000000000000000000000000000000000000000000000000000000 { // delegatecall = true value = false
result := delegatecall(gas(), target, 0, callSize, 0, 0)
}
case 0x00000300000000000000000000000000000000000000000000000000000000000000 { // dynamicvalue = true
let value := calldataload(offset)
offset := add(0x20, offset)
result := call(gas(), target, value, 0, callSize, 0, 0)
}
default{ // something is broken
revert(0,0)
}
if eq(result, 0) { // stop on error
break
}
}
// return or revert based on value of result
let size := returndatasize()
returndatacopy(0, 0, size)
if eq(result, 0) {
revert(0, size)
}
return(0, size)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment