weiroll / weiroll Goto Github PK
View Code? Open in Web Editor NEWThe weiroll virtual machine
License: MIT License
The weiroll virtual machine
License: MIT License
Instead, we should just require all dynamic state entries to be a multiple of 32 bytes long.
If the given index, idx & IDX_VALUE_MASK
, is out of bounds for the state
array, the following inline assembly writes out of bounds, and may corrupt the state
array, which may be exploited.
weiroll/contracts/CommandBuilder.sol
Lines 126 to 129 in ebc6d70
If the new entry is the same length as the existing one, we can overwrite it instead of allocating a new one.
If the new entry is shorter, we can also overwrite it - and update the length field.
Most of the remaining assembly is one of a few operations such as reading or writing a bytes32 to/from a bytes string. We should abstract these into helper functions so the code is clearer.
Instead of indexing into a single 127-value state array, you can have an arbitrary length state array and allow indexing from either the start or from the end, up to 64 items.
The basic idea is that most terms get reduced, and the terms that are reused frequently are computed early in the trace. Essentially you have 64 'globals' (including arguments) and 64 'locals'. The 'locals' fall out of scope after 64 operations. The globals stay accessible for the whole script.
Hi,
Maybe I am misunderstanding something, but I don't know how to do this..
contract First {
struct Perm {
address a1;
bytes32 a2;
}
function callFirst() public returns(Perm[] memory perm) {
perm = new Perm[](1);
perm[0] = Perm(address(0), bytes32(0));
}
}
contract Second {
struct Perm {
address a1;
bytes32 a2;
}
function callSecond(uint b1, Perm[] memory p) public modifierProtected {
// use parameters
}
}
contract Third {
struct Action {
address to;
value: 0;
bytes data;
}
function great(uint k, Action[] memory ac) public {
for(uint i = 0; i < ac.length; i++){
ac[i].to.call{}(data);
}
}
}
So, idea is, first command is calling callFirst
, it will return Perm[]
but I can't call Second
only Third can do it, so, second command must be great
. but I don't know how to encode ret
inside Action
's data.
let ret = planner.add(firstContract.callFirst());
planner.add(thirdContract.great([{
to: "address of second contract",
value: 0,
// NOTE it fails here as it can't do encode.
data: ethers.utils.defaultAbiCoder.encode(["uint", "tuple(uint8,address,address,bytes32)[]"], [5, ret])
}]));
isn't this possible ? problem is Third
can't accept Perm[]
directly (can't change it) and I also don't wanna create/deploy middle contract that will get the call, encode the stuff and then redirect it back to ThIrd
which then redirects to Second.
Any idea ?
Structs that only contain 32 bytes types are not encoded with a pointer. So when the planner calls hexDataSlice
here it is actually removing the first value of the struct. And then when the pointer gets added back in CommandBuilder.sol, it is replacing the first value of the struct with the pointer value.
For example:
struct Numbers {
uint256 a;
uint256 b;
}
This value: { a: 1, b: 2 }
, gets encoded like this:
0x
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000002
As you can see there are just the two uint256's and no pointer. After passing through planner.ts and CommandBuilder.sol, this is the return value (I've removed the sig hash for clarity):
0x
0000000000000000000000000000000000000000000000000000000000000020
0000000000000000000000000000000000000000000000000000000000000002
You can see failing tests on this commit: https://github.com/PeterMPhillips/weiroll/commit/5a5da12f55147ff3eca6d18816d4bdc5b0f66892.
A similar issue can be seen when we have a struct that is a mixture of address
and uint256
, for example:
struct Mixed {
uint256 a;
address b;
}
Or the structs that are used by Uniswap V3: https://github.com/Uniswap/v3-periphery/blob/v1.0.0/contracts/interfaces/ISwapRouter.sol#L10
One of the reserved bits could mean "don't rethrow exceptions, instead record result type"
It could be encoded as a pair, or it could be encoded using multiple return[1], or even 'flat' multiple return with call result flag as output 0 and the actual output at 1-n
We should have a library that emits various events when called. We should then remove the event from the executor and update all the tests to use the library instead.
This should save significant gas by cutting down on wasted encoding overhead for bytes[]
full of fixed-length data.
If Executor is directly called without delegatecall, an operation it calls out to could call selfdestruct() and destroy the executor contract.
A guard has to be built in via a code-stored immutable var that holds the deployed contract address and checks address(this) != self
prior to running any commands.
Implement:
extractDynamicElement
: reads two offsets from words index
and nextIndex
, and returns that segment of the data. If nextIndex
is zero, it returns the slice from index to the end of the string.
...rather than manually composing the command words and state.
Suggest defining ext
to give 16 inputs and 16 outputs in the next word, rather than 32 extra inputs
There are a few core functionalities called through specific opcodes that are often used and Executor takes a large gas hit of 5k per call if these are implemented in libraries, whereas most of the opcodes have negligible gas cost if called directly.
List to consider implementing inside Executor directly as special case functions:
Since many of these opcodes take no arguments or just one fixed-size argument, we could extend the flags bit to specify opcode call, and use immediate of the next command as the word for the argument if needed.
Main exceptions are CREATE/CREATE2 opcodes and SHA3, which can take arbitrary length data.
SHA3 costs 30 gas + 6*#words for hashing data, so for most calls it is far less than 5k, and probably worth special casing.
CREATE/CREATE2 might not be worth it, and the use case of creating a contract through executor is dubious.
We should write a thorough set of tests for the executor, particularly testing out edge/failure cases.
And figure out how we can optimise it further.
in
section to be 6 bytes wide (5,6,7,8,9,0): https://github.com/weiroll/weiroll#command-structurein
values:
f
(both times 0x00
) to be part of in
?Instead of having a special 'raw call' function signature, we could reserve a register value as meaning 'the whole state'. When we see it, serialize the state (for call arguments) or deserialize and replace it (for return values).
In README, tup
is at MSB, and ext
is the next:
0 1 2 3 4 5 6 7
┌───┬───┬───────────────┬────────┐
│tup│ext│ reserved │calltype│
└───┴───┴───────────────┴────────┘
but in the code, ext
is at MSB, and tup
is the next:
Lines 12 to 13 in ebc6d70
Users who encode flags following the README would experience unexpected behaviors.
We should have a library with common ERC20 ops:
We should write a thorough set of CommandBuilder tests based on wrapping it in a contract to expose the necessary functions.
When a call reverts, we should propagate the reason, along with some information about which command reverted (the index). Perhaps using the new Solidity 0.8.5 error objects?
First, thanks for sharing this project! I can't wait to try it out.
Second, I plan to integrate this project in the future with my own, especially with a rules engine written in Solidity called Wonka. There are several iterations of the engine contract(s), with the latest intended for deployment to Optimism (if that's possible or feasible). Basically, there are standard operators built into this engine (like addition) and custom operators (i.e., calling other contract methods) that can both be instantiated and used by the user of the engine, with the custom operators being defined by the user beforehand.
So my plan would be to integrate the weiroll contracts to enhance the custom operators, since it's using an inefficient approach right now. Out of curiosity, what do you think of such a plan? Do any immediate problems come to mind? Other than building a rules engine in Solidity seems somewhat crazy. :)
Thanks for any feedback!
Here we have a general purpose eth call statement grammar: https://github.com/ricobank/apes
You can read a sequence of apelines and the resulting AST is close to perfect intermediate representation for weiroll
We should have a library with common ERC721 operations:
We should have a library that allows the caller to operate on their ether balance - send it, possibly other operations?
Perhaps this library should include WETH wrap/unwrap operations?
var == 0b
implicitly means "0" but var == 1b
essentially means "10000000".ws == 1b
is the idx
of an input and so it is actually "1000000" (length=7 and not length=8)We should have a library with common ERC1155 operations:
Currently all the tests just have the EOA call directly into Executor
, but this isn't how it should be used in the general case as otherwise Executor
is msg.sender
if it calls out directly to a contract, and it is msg.sender
also when any Libs call out to contracts.
The msg.sender
for the receiver of an Executor
outcall should be the user's contract wallet.
A simple contract like the following should suffice for test purposes:
contract WalletProxy {
function forward(address target, bytes memory execdata) public returns (bytes memory){
(bool success, bytes memory out) = target.delegatecall(execdata);
if (!success){
revert("Executor call failed"); // or grab the revert reason from delegatecall
}
return out;
}
}
We can just use a wrapper to redirect the final planner.plan()
call in the tests to set up the right args/target.
Decoded structure of below test case planner call.
// Commands Array
selector flag inputs output target address
[ ----4----- -1 --------6-------- -1 ----------------20----------------------
'0xe63697c8 01 00 01 02 ff ff ff ff 5f18c75abdae578b483e5f43f12a39cf75b973a9', --- call
'0x70a08231 02 01 ff ff ff ff ff 01 a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', --- staticall
'0x85f45c88 81 03 ff ff ff ff ff 83 e909128d38077bebd996692916865a5c24bfb522', --- call
'0x77e2eefa 02 83 01 00 ff ff ff ff c6e7df5e7b4f2a278906862b61205850344d4e7d' --- statically
]
// State array
[
'0x0000000000000000000000000000000000000000000000000000002df2362124',
'0x00000000000000000000000059b670e9fa9d0a427751af201d676719a970857b',
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8'
]
----------------------------------------------------------------------------------------------
Command 1 input -
[0x0000000000000000000000000000000000000000000000000000002df2362124, -- shares
0x00000000000000000000000059b670e9fa9d0a427751af201d676719a970857b, -- position address
0x0000000000000000000000000000000000000000000000000000000000000000 -- 0
]
output - neglected
----------------------------------------------------------------------------------------------
Command 2 input -
[0x00000000000000000000000059b670e9fa9d0a427751af201d676719a970857b] -- position address
output -
Output added at index 1 because of `01` output flag value
----------------------------------------------------------------------------------------------
Command 3 input -
[0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8] -- user address
output -
It should update the state at index 3 because of `0x83` output flag value,i.e. 10000011 where MSB `1` represent dynamic length. value while `0000011` should represent the index in state array i.e 3 while in the code
https://github.com/weiroll/weiroll/blob/bec31fdebf1eac6ed0c8e9e6aa768ec361e2c9c7/contracts/VM.sol#L115 we are using the
decimal value of `0x83` as the index instead of 7 LSB.
----------------------------------------------------------------------------------------------
Command 4 input is
[
<Output from command 3>, -- tuple value
<Output from command 2>, -- balance queried in 2nd command
0x0000000000000000000000000000000000000000000000000000002df2362124 -- shares
]
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.