Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
3b288e0
Add first version deposit contract
axic Apr 24, 2026
7958192
Fix to_little_endian_64
axic Apr 29, 2026
498a071
more implementaton of deposit
axic Apr 30, 2026
6111783
Use memorybytes
axic Apr 30, 2026
bc79bbb
Add more checks
axic Apr 30, 2026
2d4c2ec
Use bytes32 in deposit
axic May 8, 2026
4617b1b
Update to new import style
axic May 8, 2026
7151ce2
Use uint256 division
axic May 8, 2026
8b847f1
Testrunner debugging
axic May 8, 2026
ebbd8a7
Update deposit test with real data
axic May 8, 2026
d20dde6
Add missing loops to deposit
axic May 8, 2026
15ea86c
is_odd and syntax
axic May 9, 2026
0701361
f
axic May 10, 2026
1ab02da
Remove unrolled loops
axic May 10, 2026
7f485ed
Reenable require
axic May 10, 2026
f059f1b
update tests
axic May 10, 2026
ebead5a
Add bytes_slice and bytes_truncate
axic May 10, 2026
4086261
Fix memory concat
axic May 10, 2026
59688d2
Remove debugging code
axic May 10, 2026
e047d52
more tests
axic May 10, 2026
db75238
max_count
axic May 10, 2026
4062d68
andd expected root to test
axic May 10, 2026
4a5681e
fix calcualted root
axic May 10, 2026
0d93199
Use bytes32 everywhere
axic May 10, 2026
d6791cf
Add log1
axic May 10, 2026
b3231b2
Use mstore helpers
axic May 10, 2026
7b8f486
Remove is_odd
axic May 10, 2026
3e61b92
use uint256 in comparisons and not word
axic May 10, 2026
9119036
Check count after deposit in tests
axic May 10, 2026
ea15250
Check that root/count is unchagned after failed deposit
axic May 10, 2026
7b408d7
Add zeroize_memory helper
axic May 10, 2026
ceb1684
use std.mload
axic May 11, 2026
f11f522
Add explicit return types
axic May 15, 2026
11b925e
Add class ToBytesLike
axic May 15, 2026
d703c2a
Add a new version of concat
axic May 15, 2026
5ca11ac
New version of to_bytes
axic May 15, 2026
ca60412
use new to_bytes/concat
axic May 15, 2026
bd771a5
Add empty(word)
axic May 15, 2026
45d6fe2
Variadic
axic May 17, 2026
0d413db
remove assert/require
axic Jun 1, 2026
14a2a8f
Revert "Variadic"
axic Jun 1, 2026
7e63628
rename to myrequire
axic Jun 1, 2026
8d8930e
mark it payable
axic Jun 2, 2026
f6f1be4
Remove empty() (in std now)
axic Jun 9, 2026
5a612f0
Remove MemoryAttributes (in std now)
axic Jun 9, 2026
fa9c642
Remove sha256 (in std now)
axic Jun 9, 2026
e583718
Use MemoryAttributes over MemoryHelpers
axic Jun 9, 2026
9632a53
Use concat/bytes from std
axic Jun 9, 2026
7bfeab5
Use zeroize_memory from std
axic Jun 9, 2026
7e9d752
Drop obsolete to_bytes
axic Jun 9, 2026
338e12d
Use callvalue from opcodes
axic Jun 9, 2026
8267b3b
Drop unused assert
axic Jun 9, 2026
f392338
Use usptream require
axic Jun 9, 2026
53b283f
Use new split
axic Jun 9, 2026
ebfca97
Fix test
axic Jun 9, 2026
63aa0c8
Add bytes_slice abstraction
axic Jun 9, 2026
db9c81f
Rwerite bytes_slice/bytes_truncate with new abstraction
axic Jun 9, 2026
2339128
Sliceable abstraction
axic Jun 9, 2026
f68845f
Make sha256 generic
axic Jun 9, 2026
b979cd0
Greatly simplify bytes_slice
axic Jun 9, 2026
84aae04
Generalize even further
axic Jun 9, 2026
87ed263
Use upstream slice_/truncate
axic Jun 12, 2026
b239676
Comments
axic Jun 12, 2026
5ff1a10
Use proper error codes
axic Jun 12, 2026
e5362a5
Use +=
axic Jun 12, 2026
cab2841
Mark functions public
axic Jun 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions run_contests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ bash ./contest.sh test/examples/dispatch/slices.json
bash ./contest.sh test/examples/dispatch/fallback.json
bash ./contest.sh test/examples/dispatch/ecrecover.json
bash ./contest.sh test/examples/dispatch/memory.json
bash ./contest.sh test/examples/dispatch/deposit.json
3 changes: 2 additions & 1 deletion test/Cases.hs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ dispatches =
runDispatchTest "Revert.solc",
runDispatchTest "hashes.solc",
runDispatchTest "empty.solc",
runDispatchTest "empty_no_constructor.solc"
runDispatchTest "empty_no_constructor.solc",
runDispatchTest "deposit.solc"
]
where
runDispatchTest file = runTestForFileWith (emptyOption mempty) file "./test/examples/dispatch"
Expand Down
214 changes: 214 additions & 0 deletions test/examples/dispatch/deposit.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
{
"depositcontract": {
"bytecode": "_CODE",
"contract": "DepositContract",
"tests": [
{
"input": {
"comment": "constructor()",
"calldata": "",
"value": "0"
},
"kind": "constructor"
},

{
"input": {
"comment": "get_zero_hash(0)",
"calldata": "43ca34020000000000000000000000000000000000000000000000000000000000000000",
"value": "0"
},
"kind": "call",
"output": {
"returndata": "0000000000000000000000000000000000000000000000000000000000000000",
"status": "success"
}
},

{
"input": {
"comment": "get_zero_hash(1)",
"calldata": "43ca34020000000000000000000000000000000000000000000000000000000000000001",
"value": "0"
},
"kind": "call",
"output": {
"returndata": "f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b",
"status": "success"
}
},


{
"input": {
"comment": "get_zero_hash(2)",
"calldata": "43ca34020000000000000000000000000000000000000000000000000000000000000002",
"value": "0"
},
"kind": "call",
"output": {
"returndata": "db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71",
"status": "success"
}
},


{
"input": {
"comment": "get_zero_hash(3)",
"calldata": "43ca34020000000000000000000000000000000000000000000000000000000000000003",
"value": "0"
},
"kind": "call",
"output": {
"returndata": "c78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c",
"status": "success"
}
},


{
"input": {
"comment": "get_zero_hash(4)",
"calldata": "43ca34020000000000000000000000000000000000000000000000000000000000000004",
"value": "0"
},
"kind": "call",
"output": {
"returndata": "536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c",
"status": "success"
}
},

{
"input": {
"comment": "get_zero_hash(15)",
"calldata": "43ca3402000000000000000000000000000000000000000000000000000000000000000f",
"value": "0"
},
"kind": "call",
"output": {
"returndata": "d49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb",
"status": "success"
}
},

{
"input": {
"comment": "get_zero_hash(31)",
"calldata": "43ca3402000000000000000000000000000000000000000000000000000000000000001f",
"value": "0"
},
"kind": "call",
"output": {
"returndata": "985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7",
"status": "success"
}
},

{
"input": {
"comment": "get_deposit_root()",
"calldata": "c5f2892f",
"value": "0"
},
"kind": "call",
"output": {
"returndata": "d70a234731285c6804c2a4f56711ddb8c82c99740f207854891028af34e27e5e",
"status": "success"
}
},

{
"input": {
"comment": "get_deposit_count()",
"calldata": "621fd130",
"value": "0"
},
"kind": "call",
"output": {
"returndata": "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000",
"status": "success"
}
},

{
"input": {
"comment": "deposit(bytes,bytes,bytes,bytes32)",
"calldata": "22895118000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120d2b73e995b4c94bf83bcbf58cb9dede2c5e0211ec61eeb09c43aeaad97ce02620000000000000000000000000000000000000000000000000000000000000030abe405f4ca553d02da780b187e15c767fc89d4f9a707bd54c98a8d8e401124dedc6bc14dcdf0ed690e9ec424deae49a50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200100000000000000000000007e2a2fa2a064f693f0a55c5639476d913ff12d0500000000000000000000000000000000000000000000000000000000000000608927fa063b70c19a28c3c78aa19bbb2e7b971fc1b8de2033930952a11ef17709348cc2cc14ae810ab249a1fe39e959470b850608afc299811c5cce190f92dd79529ba11846c138073ae200782476c8e5e4c6ea3b5938a16398835e4964c6bac0",
"value": "32000000000000000000"
},
"kind": "call",
"output": {
"returndata": "",
"status": "success"
}
},

{
"input": {
"comment": "get_deposit_root()",
"calldata": "c5f2892f",
"value": "0"
},
"kind": "call",
"output": {
"returndata": "d3014e96ac4ef15ccbac8047549cac09ad687f6169cba901b92c8875a62ca917",
"status": "success"
}
},

{
"input": {
"comment": "get_deposit_count()",
"calldata": "621fd130",
"value": "0"
},
"kind": "call",
"output": {
"returndata": "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000080100000000000000000000000000000000000000000000000000000000000000",
"status": "success"
}
},

{
"input": {
"comment": "deposit(bytes,bytes,bytes,bytes32)",
"calldata": "22895118000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120d2b73e995b4c94bf83bcbf58cb9dede2c5e0211ec61eeb09c43aeaad97ce02620000000000000000000000000000000000000000000000000000000000000030abe405f4ca553d02da780b187e15c767fc89d4f9a707bd54c98a8d8e401124dedc6bc14dcdf0ed690e9ec424deae49a50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200100000000000000000000007e2a2fa2a064f693f0a55c5639476d913ff12d0500000000000000000000000000000000000000000000000000000000000000608927fa063b70c19a28c3c78aa19bbb2e7b971fc1b8de2033930952a11ef17709348cc2cc14ae810ab249a1fe39e959470b850608afc299811c5cce190f92dd79529ba11846c138073ae200782476c8e5e4c6ea3b5938a16398835e4964c6bac0",
"value": "0"
},
"kind": "call",
"output": {
"returndata": "bdfc7472",
"status": "failure"
}
},

{
"input": {
"comment": "get_deposit_root()",
"calldata": "c5f2892f",
"value": "0"
},
"kind": "call",
"output": {
"returndata": "d3014e96ac4ef15ccbac8047549cac09ad687f6169cba901b92c8875a62ca917",
"status": "success"
}
},

{
"input": {
"comment": "get_deposit_count()",
"calldata": "621fd130",
"value": "0"
},
"kind": "call",
"output": {
"returndata": "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000080100000000000000000000000000000000000000000000000000000000000000",
"status": "success"
}
}
]
}
}
163 changes: 163 additions & 0 deletions test/examples/dispatch/deposit.solc
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import std.{*};
import std.dispatch.{*};
import std.opcodes.{callvalue};

// TODO: Should use uint64.
// Assumes 64-bit input.
function to_little_endian_64(v: uint256) -> memory(bytes) {
let res: word = allocate_memory(32 + 8);
let value: word = Typedef.rep(v);
assembly {
mstore(res, 8)
mstore8(add(res, 32), and(value, 0xff))
mstore8(add(res, 33), and(shr(8, value), 0xff))
mstore8(add(res, 34), and(shr(16, value), 0xff))
mstore8(add(res, 35), and(shr(24, value), 0xff))
mstore8(add(res, 36), and(shr(32, value), 0xff))
mstore8(add(res, 37), and(shr(40, value), 0xff))
mstore8(add(res, 38), and(shr(48, value), 0xff))
mstore8(add(res, 39), and(shr(56, value), 0xff))
}
return memory(res);
}

// No constants are supported yet, using this as a workaround.
// Defining variables outside of contract/function is not supported.
function DEPOSIT_CONTRACT_TREE_DEPTH() -> uint256 {
return uint256(32);
}

function MAX_DEPOSIT_COUNT() -> uint256 {
// uint constant MAX_DEPOSIT_COUNT = 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1;
// Could use Bounded(uint64).maxVal()
return uint256(0xFFFFFFFF);
}

contract DepositContract {
deposit_count : uint256;
// TODO: need storage array support
// branch: array(uint256, DEPOSIT_CONTRACT_TREE_DEPTH());
// zero_hashes : array(uint256, DEPOSIT_CONTRACT_TREE_DEPTH());
// misusing mappings here, the index is the key
branch : mapping(uint256, bytes32);
zero_hashes : mapping(uint256, bytes32);

constructor() {
// Compute hashes in empty sparse Merkle tree
for (let height = uint256(0); height < (DEPOSIT_CONTRACT_TREE_DEPTH() - uint256(1)); height += uint256(1)) {
zero_hashes[height + uint256(1)] = sha256(concat(zero_hashes[height], zero_hashes[height]));
}
}

// TODO: this is for testing only
public function get_zero_hash(index: uint256) -> bytes32 {
return zero_hashes[index];
}

public function get_deposit_root() -> bytes32 {
let node: bytes32;
let size = deposit_count;
for (let height = uint256(0); height < DEPOSIT_CONTRACT_TREE_DEPTH(); height += uint256(1)) {
//if ((size & 1) == 1) {
if ((size % uint256(2)) == uint256(1)) {
node = sha256(concat(branch[height], node));
} else {
node = sha256(concat(node, zero_hashes[height]));
}
size = size / uint256(2);
}
return sha256(concat(
concat(
node,
to_little_endian_64(deposit_count)
),
empty(24)
));
}

public function get_deposit_count() -> memory(bytes) {
return to_little_endian_64(deposit_count);
}

// TODO: once string literals are properly supported, change errors to messages
// matching the deposit contract, full 100% identical behaviour.
public payable function deposit(pubkey: memory(bytes), withdrawal_credentials: memory(bytes), signature: memory(bytes), deposit_data_root: bytes32) -> () {
// Extended ABI length checks since dynamic types are used.
require(MemorySize.len(pubkey) == 48, Error(0x9ca717ed)); // InvalidPubkeyLength()
require(MemorySize.len(withdrawal_credentials) == 32, Error(0x3debbf1e)); // InvalidWithdrawalCredentialsLength()
require(MemorySize.len(signature) == 96, Error(0x4be6321b)); // InvalidSignatureLength()

// Check deposit amount
// >= 1ether
require(callvalue() >= 1000000000000000000, Error(0xbdfc7472)); // DepositValueTooLow()
// % 1 gwei == 0
require((callvalue() % 1000000000) == 0, Error(0x9c7417e9)); // DepositValueNotMultipleOfOneGwei()

let deposit_amount = callvalue() / 1000000000; // 1 gwei
// <= type(uint64).max
require(deposit_amount <= 0xffffffffffffffff, Error(0x2aa66734)); // DepositValueTooHigh()

let amount: memory(bytes) = to_little_endian_64(uint256(deposit_amount));
// TODO: emit DepositEvent
/*
event DepositEvent(
bytes pubkey,
bytes withdrawal_credentials,
bytes amount,
bytes signature,
bytes index
);

emit DepositEvent(
pubkey,
withdrawal_credentials,
amount,
signature,
to_little_endian_64(uint64(deposit_count))
);
*/
assembly {
// TODO: need to ABI-encode all the arguments
log1(0, 0, 0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5)
}

// Compute deposit data root (`DepositData` hash tree root)
let pubkey_root = sha256(concat(pubkey, empty(16)));
let signature_root = sha256(concat(
sha256(truncate(signature, 64)), // slice
sha256(concat(slice_(signature, 64), empty(32)))
));
let node = sha256(concat(
sha256(concat(pubkey_root, withdrawal_credentials)),
sha256(concat(concat(amount, empty(24)), signature_root))
));

// Verify computed and expected deposit data roots match
require(node == deposit_data_root, Error(0x2ec2f183)); // ReconstructedDepositDataMismatch()

// Avoid overflowing the Merkle tree (and prevent edge case in computing `branch`)
require(deposit_count < MAX_DEPOSIT_COUNT(), Error(0xef5ccf66)); // MerkleTreeFull()

// Add deposit data root to Merkle tree (update a single `branch` node)
deposit_count += uint256(1);
let size = deposit_count;
for (let height = uint256(0); height < DEPOSIT_CONTRACT_TREE_DEPTH(); height += uint256(1)) {
//if ((size & 1) == 1) {
if ((size % uint256(2)) == uint256(1)) {
branch[height] = node;
return ();
}
node = sha256(concat(branch[height], node));
size = size / uint256(2);
}

// As the loop should always end prematurely with the `return` statement,
// this code should be unreachable. We assert `false` just to be safe.
assert(false);
}

// TODO: use bytes4
public function supportsInterface(interfaceId: uint256) -> bool {
return false;
}
}
Loading
Loading