From 448af6af7ba1e7ca5303eded124f5b2c3e153f0c Mon Sep 17 00:00:00 2001 From: yiplee Date: Fri, 3 Dec 2021 22:37:09 +0800 Subject: [PATCH 1/2] mod: _ --- cmd/broadcast/tmpl/hodl.10.01.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/broadcast/tmpl/hodl.10.01.yaml b/cmd/broadcast/tmpl/hodl.10.01.yaml index 3d8add2..23b5790 100644 --- a/cmd/broadcast/tmpl/hodl.10.01.yaml +++ b/cmd/broadcast/tmpl/hodl.10.01.yaml @@ -1,4 +1,4 @@ -id: c8d12a3e-8f90-4871-a738-716254b78d70 +id: d8efcc70-505e-4102-97ef-cbeba9cc5da3 messages: - category: PLAIN_POST From 36153c3ca22a77d7e1a0cba17d07372f704a4c10 Mon Sep 17 00:00:00 2001 From: yiplee Date: Fri, 3 Dec 2021 22:37:32 +0800 Subject: [PATCH 2/2] add eth contracts for mvm --- contracts/bytes.sol | 564 ++++++++++++++++++++++++++++++++++++++++++++ contracts/hodl.sol | 226 ++++++++++++++++++ 2 files changed, 790 insertions(+) create mode 100644 contracts/bytes.sol create mode 100644 contracts/hodl.sol diff --git a/contracts/bytes.sol b/contracts/bytes.sol new file mode 100644 index 0000000..9c5faee --- /dev/null +++ b/contracts/bytes.sol @@ -0,0 +1,564 @@ +// SPDX-License-Identifier: Unlicense +/* + * @title Solidity Bytes Arrays Utils + * @author Gonçalo Sá + * + * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity. + * The library lets you concatenate, slice and type cast bytes arrays both in memory and storage. + */ +pragma solidity >=0.8.0 <0.9.0; + +library BytesLib { + function concat(bytes memory _preBytes, bytes memory _postBytes) + internal + pure + returns (bytes memory) + { + bytes memory tempBytes; + + assembly { + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. + tempBytes := mload(0x40) + + // Store the length of the first bytes array at the beginning of + // the memory for tempBytes. + let length := mload(_preBytes) + mstore(tempBytes, length) + + // Maintain a memory counter for the current write location in the + // temp bytes array by adding the 32 bytes for the array length to + // the starting location. + let mc := add(tempBytes, 0x20) + // Stop copying when the memory counter reaches the length of the + // first bytes array. + let end := add(mc, length) + + for { + // Initialize a copy counter to the start of the _preBytes data, + // 32 bytes into its memory. + let cc := add(_preBytes, 0x20) + } lt(mc, end) { + // Increase both counters by 32 bytes each iteration. + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + // Write the _preBytes data into the tempBytes memory 32 bytes + // at a time. + mstore(mc, mload(cc)) + } + + // Add the length of _postBytes to the current length of tempBytes + // and store it as the new length in the first 32 bytes of the + // tempBytes memory. + length := mload(_postBytes) + mstore(tempBytes, add(length, mload(tempBytes))) + + // Move the memory counter back from a multiple of 0x20 to the + // actual end of the _preBytes data. + mc := end + // Stop copying when the memory counter reaches the new combined + // length of the arrays. + end := add(mc, length) + + for { + let cc := add(_postBytes, 0x20) + } lt(mc, end) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + mstore(mc, mload(cc)) + } + + // Update the free-memory pointer by padding our last write location + // to 32 bytes: add 31 bytes to the end of tempBytes to move to the + // next 32 byte block, then round down to the nearest multiple of + // 32. If the sum of the length of the two arrays is zero then add + // one before rounding down to leave a blank 32 bytes (the length block with 0). + mstore( + 0x40, + and( + add(add(end, iszero(add(length, mload(_preBytes)))), 31), + not(31) // Round down to the nearest 32 bytes. + ) + ) + } + + return tempBytes; + } + + function concatStorage(bytes storage _preBytes, bytes memory _postBytes) + internal + { + assembly { + // Read the first 32 bytes of _preBytes storage, which is the length + // of the array. (We don't need to use the offset into the slot + // because arrays use the entire slot.) + let fslot := sload(_preBytes.slot) + // Arrays of 31 bytes or less have an even value in their slot, + // while longer arrays have an odd value. The actual length is + // the slot divided by two for odd values, and the lowest order + // byte divided by two for even values. + // If the slot is even, bitwise and the slot with 255 and divide by + // two to get the length. If the slot is odd, bitwise and the slot + // with -1 and divide by two. + let slength := div( + and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), + 2 + ) + let mlength := mload(_postBytes) + let newlength := add(slength, mlength) + // slength can contain both the length and contents of the array + // if length < 32 bytes so let's prepare for that + // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage + switch add(lt(slength, 32), lt(newlength, 32)) + case 2 { + // Since the new array still fits in the slot, we just need to + // update the contents of the slot. + // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length + sstore( + _preBytes.slot, + // all the modifications to the slot are inside this + // next block + add( + // we can just add to the slot contents because the + // bytes we want to change are the LSBs + fslot, + add( + mul( + div( + // load the bytes from memory + mload(add(_postBytes, 0x20)), + // zero all bytes to the right + exp(0x100, sub(32, mlength)) + ), + // and now shift left the number of bytes to + // leave space for the length in the slot + exp(0x100, sub(32, newlength)) + ), + // increase length by the double of the memory + // bytes length + mul(mlength, 2) + ) + ) + ) + } + case 1 { + // The stored value fits in the slot, but the combined value + // will exceed it. + // get the keccak hash to get the contents of the array + mstore(0x0, _preBytes.slot) + let sc := add(keccak256(0x0, 0x20), div(slength, 32)) + + // save new length + sstore(_preBytes.slot, add(mul(newlength, 2), 1)) + + // The contents of the _postBytes array start 32 bytes into + // the structure. Our first read should obtain the `submod` + // bytes that can fit into the unused space in the last word + // of the stored array. To get this, we read 32 bytes starting + // from `submod`, so the data we read overlaps with the array + // contents by `submod` bytes. Masking the lowest-order + // `submod` bytes allows us to add that value directly to the + // stored value. + + let submod := sub(32, slength) + let mc := add(_postBytes, submod) + let end := add(_postBytes, mlength) + let mask := sub(exp(0x100, submod), 1) + + sstore( + sc, + add( + and( + fslot, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00 + ), + and(mload(mc), mask) + ) + ) + + for { + mc := add(mc, 0x20) + sc := add(sc, 1) + } lt(mc, end) { + sc := add(sc, 1) + mc := add(mc, 0x20) + } { + sstore(sc, mload(mc)) + } + + mask := exp(0x100, sub(mc, end)) + + sstore(sc, mul(div(mload(mc), mask), mask)) + } + default { + // get the keccak hash to get the contents of the array + mstore(0x0, _preBytes.slot) + // Start copying to the last used word of the stored array. + let sc := add(keccak256(0x0, 0x20), div(slength, 32)) + + // save new length + sstore(_preBytes.slot, add(mul(newlength, 2), 1)) + + // Copy over the first `submod` bytes of the new data as in + // case 1 above. + let slengthmod := mod(slength, 32) + let mlengthmod := mod(mlength, 32) + let submod := sub(32, slengthmod) + let mc := add(_postBytes, submod) + let end := add(_postBytes, mlength) + let mask := sub(exp(0x100, submod), 1) + + sstore(sc, add(sload(sc), and(mload(mc), mask))) + + for { + sc := add(sc, 1) + mc := add(mc, 0x20) + } lt(mc, end) { + sc := add(sc, 1) + mc := add(mc, 0x20) + } { + sstore(sc, mload(mc)) + } + + mask := exp(0x100, sub(mc, end)) + + sstore(sc, mul(div(mload(mc), mask), mask)) + } + } + } + + function slice( + bytes memory _bytes, + uint256 _start, + uint256 _length + ) internal pure returns (bytes memory) { + require(_length + 31 >= _length, "slice_overflow"); + require(_bytes.length >= _start + _length, "slice_outOfBounds"); + + bytes memory tempBytes; + + assembly { + switch iszero(_length) + case 0 { + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. + tempBytes := mload(0x40) + + // The first word of the slice result is potentially a partial + // word read from the original array. To read it, we calculate + // the length of that partial word and start copying that many + // bytes into the array. The first word we copy will start with + // data we don't care about, but the last `lengthmod` bytes will + // land at the beginning of the contents of the new array. When + // we're done copying, we overwrite the full first word with + // the actual length of the slice. + let lengthmod := and(_length, 31) + + // The multiplication in the next line is necessary + // because when slicing multiples of 32 bytes (lengthmod == 0) + // the following copy loop was copying the origin's length + // and then ending prematurely not copying everything it should. + let mc := add( + add(tempBytes, lengthmod), + mul(0x20, iszero(lengthmod)) + ) + let end := add(mc, _length) + + for { + // The multiplication in the next line has the same exact purpose + // as the one above. + let cc := add( + add( + add(_bytes, lengthmod), + mul(0x20, iszero(lengthmod)) + ), + _start + ) + } lt(mc, end) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + mstore(mc, mload(cc)) + } + + mstore(tempBytes, _length) + + //update free-memory pointer + //allocating the array padded to 32 bytes like the compiler does now + mstore(0x40, and(add(mc, 31), not(31))) + } + //if we want a zero-length slice let's just return a zero-length array + default { + tempBytes := mload(0x40) + //zero out the 32 bytes slice we are about to return + //we need to do it because Solidity does not garbage collect + mstore(tempBytes, 0) + + mstore(0x40, add(tempBytes, 0x20)) + } + } + + return tempBytes; + } + + function toAddress(bytes memory _bytes, uint256 _start) + internal + pure + returns (address) + { + require(_bytes.length >= _start + 20, "toAddress_outOfBounds"); + address tempAddress; + + assembly { + tempAddress := div( + mload(add(add(_bytes, 0x20), _start)), + 0x1000000000000000000000000 + ) + } + + return tempAddress; + } + + function toUint8(bytes memory _bytes, uint256 _start) + internal + pure + returns (uint8) + { + require(_bytes.length >= _start + 1, "toUint8_outOfBounds"); + uint8 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x1), _start)) + } + + return tempUint; + } + + function toUint16(bytes memory _bytes, uint256 _start) + internal + pure + returns (uint16) + { + require(_bytes.length >= _start + 2, "toUint16_outOfBounds"); + uint16 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x2), _start)) + } + + return tempUint; + } + + function toUint32(bytes memory _bytes, uint256 _start) + internal + pure + returns (uint32) + { + require(_bytes.length >= _start + 4, "toUint32_outOfBounds"); + uint32 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x4), _start)) + } + + return tempUint; + } + + function toUint64(bytes memory _bytes, uint256 _start) + internal + pure + returns (uint64) + { + require(_bytes.length >= _start + 8, "toUint64_outOfBounds"); + uint64 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x8), _start)) + } + + return tempUint; + } + + function toUint96(bytes memory _bytes, uint256 _start) + internal + pure + returns (uint96) + { + require(_bytes.length >= _start + 12, "toUint96_outOfBounds"); + uint96 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0xc), _start)) + } + + return tempUint; + } + + function toUint128(bytes memory _bytes, uint256 _start) + internal + pure + returns (uint128) + { + require(_bytes.length >= _start + 16, "toUint128_outOfBounds"); + uint128 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x10), _start)) + } + + return tempUint; + } + + function toUint256(bytes memory _bytes, uint256 _start) + internal + pure + returns (uint256) + { + require(_bytes.length >= _start + 32, "toUint256_outOfBounds"); + uint256 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x20), _start)) + } + + return tempUint; + } + + function toBytes32(bytes memory _bytes, uint256 _start) + internal + pure + returns (bytes32) + { + require(_bytes.length >= _start + 32, "toBytes32_outOfBounds"); + bytes32 tempBytes32; + + assembly { + tempBytes32 := mload(add(add(_bytes, 0x20), _start)) + } + + return tempBytes32; + } + + function equal(bytes memory _preBytes, bytes memory _postBytes) + internal + pure + returns (bool) + { + bool success = true; + + assembly { + let length := mload(_preBytes) + + // if lengths don't match the arrays are not equal + switch eq(length, mload(_postBytes)) + case 1 { + // cb is a circuit breaker in the for loop since there's + // no said feature for inline assembly loops + // cb = 1 - don't breaker + // cb = 0 - break + let cb := 1 + + let mc := add(_preBytes, 0x20) + let end := add(mc, length) + + for { + let cc := add(_postBytes, 0x20) + // the next line is the loop condition: + // while(uint256(mc < end) + cb == 2) + } eq(add(lt(mc, end), cb), 2) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + // if any of these checks fails then arrays are not equal + if iszero(eq(mload(mc), mload(cc))) { + // unsuccess: + success := 0 + cb := 0 + } + } + } + default { + // unsuccess: + success := 0 + } + } + + return success; + } + + function equalStorage(bytes storage _preBytes, bytes memory _postBytes) + internal + view + returns (bool) + { + bool success = true; + + assembly { + // we know _preBytes_offset is 0 + let fslot := sload(_preBytes.slot) + // Decode the length of the stored array like in concatStorage(). + let slength := div( + and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), + 2 + ) + let mlength := mload(_postBytes) + + // if lengths don't match the arrays are not equal + switch eq(slength, mlength) + case 1 { + // slength can contain both the length and contents of the array + // if length < 32 bytes so let's prepare for that + // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage + if iszero(iszero(slength)) { + switch lt(slength, 32) + case 1 { + // blank the last byte which is the length + fslot := mul(div(fslot, 0x100), 0x100) + + if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) { + // unsuccess: + success := 0 + } + } + default { + // cb is a circuit breaker in the for loop since there's + // no said feature for inline assembly loops + // cb = 1 - don't breaker + // cb = 0 - break + let cb := 1 + + // get the keccak hash to get the contents of the array + mstore(0x0, _preBytes.slot) + let sc := keccak256(0x0, 0x20) + + let mc := add(_postBytes, 0x20) + let end := add(mc, mlength) + + // the next line is the loop condition: + // while(uint256(mc < end) + cb == 2) + for { + + } eq(add(lt(mc, end), cb), 2) { + sc := add(sc, 1) + mc := add(mc, 0x20) + } { + if iszero(eq(sload(sc), mload(mc))) { + // unsuccess: + success := 0 + cb := 0 + } + } + } + } + } + default { + // unsuccess: + success := 0 + } + } + + return success; + } +} diff --git a/contracts/hodl.sol b/contracts/hodl.sol new file mode 100644 index 0000000..9134e5a --- /dev/null +++ b/contracts/hodl.sol @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.4 <0.9.0; + +import {BytesLib} from "./bytes.sol"; + +contract Hodl { + using BytesLib for bytes; + + event MixinTransaction(bytes); + event MixinEvent(bytes); + + uint128 public constant PID = 0xd402f2c61ba04d82a75c5674fae1acca; + uint64 public NONCE = 0; + mapping(uint128 => uint256) public custodian; + mapping(address => bytes) public members; + + struct vat { + uint128 asset; // lock asset + uint256 amount; // lock amount + address guy; // the owner + uint64 end; // vat expiry time [unix epoch time] + uint64 nonce; + } + + mapping(uint256 => vat) public vats; + uint256 tick = 0; + + function lock( + uint128 asset, + uint256 amount, + address guy, + uint64 end, + uint64 nonce + ) internal returns (uint256 id) { + require(guy != address(0), "guy-not-set"); + require(tick < type(uint256).max, "overflow"); + + id = ++tick; + vats[id].asset = asset; + vats[id].amount = amount; + vats[id].guy = guy; + vats[id].end = end; + vats[id].nonce = nonce; + } + + function redeem( + uint256 id, + address sender, + uint64 timestamp + ) internal { + require(vats[id].guy == sender, "not-authorized"); + require(vats[id].end < timestamp, "not-end"); + + (bytes memory extra, ) = uint256ToVarBytes(id); + bytes memory log = encodeMixinEvent( + vats[id].nonce, + vats[id].asset, + vats[id].amount, + extra, + members[sender] + ); + emit MixinTransaction(log); + + delete (vats[id]); + } + + function work( + address sender, + uint64 nonce, + uint128 asset, + uint256 amount, + uint64 timestamp, + bytes memory extra + ) internal { + uint256 offset = 0; + uint256 id = extra.toUint256(offset); + offset = offset + 32; + if (id == 0) { + uint64 exp = extra.toUint64(offset); + lock(asset, amount, sender, timestamp + exp, nonce); + } else { + redeem(id, sender, timestamp); + } + } + + // process || nonce || asset || amount || extra || timestamp || members || threshold || sig + function mixin(bytes calldata raw) public { + require(raw.length >= 141, "event data too small"); + + uint256 size = 0; + uint256 offset = 0; + uint128 process = raw.toUint128(offset); + require(process == PID, "invalid process"); + offset = offset + 16; + + uint64 nonce = raw.toUint64(offset); + require(nonce == NONCE, "invalid nonce"); + NONCE = NONCE + 1; + offset = offset + 8; + + uint128 asset = raw.toUint128(offset); + offset = offset + 16; + + size = raw.toUint16(offset); + offset = offset + 2; + require(size <= 32, "integer out of bounds"); + uint256 amount = new bytes(32 - size) + .concat(raw.slice(offset, size)) + .toUint256(0); + offset = offset + size; + + size = raw.toUint16(offset); + offset = offset + 2; + bytes memory extra = raw.slice(offset, size); + offset = offset + size; + + uint64 timestamp = raw.toUint64(offset); + offset = offset + 8; + + size = raw.toUint16(offset); + size = 2 + size * 16 + 2; + bytes memory sender = raw.slice(offset, size); + offset = offset + size; + + offset = offset + 2; + bytes memory sig = raw.slice(offset, 64); + offset = offset + 64; + require(raw.length == offset, "malformed event encoding"); + // TODO signature verification + + emit MixinEvent(raw); + custodian[asset] = custodian[asset] + amount; + members[mixinSenderToAddress(sender)] = sender; + + work( + mixinSenderToAddress(sender), + nonce, + asset, + amount, + timestamp, + extra + ); + } + + // pid || nonce || asset || amount || extra || timestamp || members || threshold || sig + function encodeMixinEvent( + uint64 nonce, + uint128 asset, + uint256 amount, + bytes memory extra, + bytes memory receiver + ) internal returns (bytes memory) { + require(extra.length < 128, "extra too large"); + require(custodian[asset] >= amount, "insufficient custodian"); + custodian[asset] = custodian[asset] - amount; + bytes memory raw = uint128ToFixedBytes(PID); + raw = raw.concat(uint64ToFixedBytes(nonce)); + raw = raw.concat(uint128ToFixedBytes(asset)); + (bytes memory ab, uint16 al) = uint256ToVarBytes(amount); + raw = raw.concat(uint16ToFixedBytes(al)); + raw = raw.concat(ab); + raw = raw.concat(uint16ToFixedBytes(uint16(extra.length))); + raw = raw.concat(extra); + raw = raw.concat(new bytes(8)); + raw = raw.concat(receiver); + raw = raw.concat(new bytes(2)); + return raw; + } + + function mixinSenderToAddress(bytes memory sender) + internal + pure + returns (address) + { + return address(uint160(uint256(keccak256(sender)))); + } + + function uint16ToFixedBytes(uint16 x) internal pure returns (bytes memory) { + bytes memory c = new bytes(2); + bytes2 b = bytes2(x); + for (uint256 i = 0; i < 2; i++) { + c[i] = b[i]; + } + return c; + } + + function uint64ToFixedBytes(uint64 x) internal pure returns (bytes memory) { + bytes memory c = new bytes(8); + bytes8 b = bytes8(x); + for (uint256 i = 0; i < 8; i++) { + c[i] = b[i]; + } + return c; + } + + function uint128ToFixedBytes(uint128 x) + internal + pure + returns (bytes memory) + { + bytes memory c = new bytes(16); + bytes16 b = bytes16(x); + for (uint256 i = 0; i < 16; i++) { + c[i] = b[i]; + } + return c; + } + + function uint256ToVarBytes(uint256 x) + internal + pure + returns (bytes memory, uint16) + { + bytes memory c = new bytes(32); + bytes32 b = bytes32(x); + uint16 offset = 0; + for (uint16 i = 0; i < 32; i++) { + c[i] = b[i]; + if (c[i] > 0 && offset == 0) { + offset = i; + } + } + uint16 size = 32 - offset; + return (c.slice(offset, 32 - offset), size); + } +}