Skip to content

Commit

Permalink
feat(ethexe): support programs exiting (#4292)
Browse files Browse the repository at this point in the history
  • Loading branch information
breathx authored Oct 22, 2024
1 parent 3099817 commit 83a2880
Show file tree
Hide file tree
Showing 18 changed files with 171 additions and 82 deletions.
1 change: 1 addition & 0 deletions ethexe/common/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub struct BlockCommitment {
pub struct StateTransition {
pub actor_id: ActorId,
pub new_state_hash: H256,
pub inheritor: ActorId,
pub value_to_receive: u128,
pub value_claims: Vec<ValueClaim>,
pub messages: Vec<OutgoingMessage>,
Expand Down
9 changes: 7 additions & 2 deletions ethexe/contracts/src/IMirror.sol
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ interface IMirror {

function stateHash() external view returns (bytes32);

function inheritor() external view returns (address);

function nonce() external view returns (uint256);

function router() external view returns (address);
Expand All @@ -87,24 +89,27 @@ interface IMirror {

function sendReply(bytes32 repliedTo, bytes calldata payload, uint128 value) external payable;

// payable?
function claimValue(bytes32 claimedId) external;

function executableBalanceTopUp(uint128 value) external payable;

function sendValueToInheritor() external;

/* Router-driven state and funds management */
// NOTE: all of these methods will have additional handler (with hooks) for decoder.

function updateState(bytes32 newStateHash) external;

function setInheritor(address inheritor) external;

function messageSent(bytes32 id, address destination, bytes calldata payload, uint128 value) external;

function replySent(address destination, bytes calldata payload, uint128 value, bytes32 replyTo, bytes4 replyCode)
external;

function valueClaimed(bytes32 claimedId, address destination, uint128 value) external;

function executableBalanceBurned(uint128 value) external;

function createDecoder(address implementation, bytes32 salt) external;

// TODO (breathx): consider removal of this in favor of separated creation and init.
Expand Down
1 change: 1 addition & 0 deletions ethexe/contracts/src/IRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ interface IRouter {
struct StateTransition {
address actorId;
bytes32 newStateHash;
address inheritor;
uint128 valueToReceive;
ValueClaim[] valueClaims;
OutgoingMessage[] messages;
Expand Down
30 changes: 24 additions & 6 deletions ethexe/contracts/src/Mirror.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
// TODO: handle ETH sent in each contract.
contract Mirror is IMirror {
bytes32 public stateHash;
address public inheritor;
// NOTE: Nonce 0 is used for init message in current implementation
uint256 public nonce; /* = 1 */
address public decoder;
Expand All @@ -25,6 +26,8 @@ contract Mirror is IMirror {

// TODO (breathx): sendMessage with msg.sender, but with tx.origin if decoder.
function sendMessage(bytes calldata _payload, uint128 _value) external payable returns (bytes32) {
require(inheritor == address(0), "program is terminated");

uint128 baseFee = IRouter(router()).baseFee();
_retrieveValueToRouter(baseFee + _value);

Expand All @@ -36,23 +39,35 @@ contract Mirror is IMirror {
}

function sendReply(bytes32 _repliedTo, bytes calldata _payload, uint128 _value) external payable {
require(inheritor == address(0), "program is terminated");

uint128 baseFee = IRouter(router()).baseFee();
_retrieveValueToRouter(baseFee + _value);

emit ReplyQueueingRequested(_repliedTo, _source(), _payload, _value);
}

function claimValue(bytes32 _claimedId) external {
// TODO (breathx): should we charge here something for try?
require(inheritor == address(0), "program is terminated");

emit ValueClaimingRequested(_claimedId, _source());
}

function executableBalanceTopUp(uint128 _value) external payable {
require(inheritor == address(0), "program is terminated");

_retrieveValueToRouter(_value);

emit ExecutableBalanceTopUpRequested(_value);
}

function sendValueToInheritor() public {
require(inheritor != address(0), "program is not terminated");

uint256 balance = IWrappedVara(IRouter(router()).wrappedVara()).balanceOf(address(this));
_sendValueTo(inheritor, uint128(balance));
}

/* Router-driven state and funds management */

function updateState(bytes32 newStateHash) external onlyRouter {
Expand All @@ -63,6 +78,13 @@ contract Mirror is IMirror {
}
}

// TODO (breathx): handle after-all transfers to program on wvara event properly.
function setInheritor(address _inheritor) external onlyRouter {
inheritor = _inheritor;

sendValueToInheritor();
}

function messageSent(bytes32 id, address destination, bytes calldata payload, uint128 value) external onlyRouter {
// TODO (breathx): handle if goes to mailbox or not. Send value in place or not.

Expand Down Expand Up @@ -113,10 +135,6 @@ contract Mirror is IMirror {
emit ValueClaimed(claimedId, value);
}

function executableBalanceBurned(uint128 value) external onlyRouter {
_sendValueTo(router(), value);
}

function createDecoder(address implementation, bytes32 salt) external onlyRouter {
require(nonce == 0, "decoder could only be created before init message");
require(decoder == address(0), "decoder could only be created once");
Expand Down Expand Up @@ -150,7 +168,7 @@ contract Mirror is IMirror {
/* Local helper functions */

function _source() private view returns (address) {
if (decoder != address(0) && msg.sender == decoder) {
if (msg.sender == decoder) {
return tx.origin;
} else {
return msg.sender;
Expand Down
10 changes: 9 additions & 1 deletion ethexe/contracts/src/Router.sol
Original file line number Diff line number Diff line change
Expand Up @@ -429,11 +429,16 @@ contract Router is IRouter, OwnableUpgradeable, ReentrancyGuardTransient {
}
}

if (stateTransition.inheritor != address(0)) {
mirrorActor.setInheritor(stateTransition.inheritor);
}

mirrorActor.updateState(stateTransition.newStateHash);

return _stateTransitionHash(
stateTransition.actorId,
stateTransition.newStateHash,
stateTransition.inheritor,
stateTransition.valueToReceive,
keccak256(valueClaimsBytes),
keccak256(messagesHashes)
Expand All @@ -452,11 +457,14 @@ contract Router is IRouter, OwnableUpgradeable, ReentrancyGuardTransient {
function _stateTransitionHash(
address actorId,
bytes32 newStateHash,
address inheritor,
uint128 valueToReceive,
bytes32 valueClaimsHash,
bytes32 messagesHashesHash
) private pure returns (bytes32) {
return keccak256(abi.encodePacked(actorId, newStateHash, valueToReceive, valueClaimsHash, messagesHashesHash));
return keccak256(
abi.encodePacked(actorId, newStateHash, inheritor, valueToReceive, valueClaimsHash, messagesHashesHash)
);
}

function _outgoingMessageHash(OutgoingMessage calldata outgoingMessage) private pure returns (bytes32) {
Expand Down
5 changes: 4 additions & 1 deletion ethexe/contracts/test/Router.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ contract RouterTest is Test {

assertEq(deployedProgram.router(), address(router));
assertEq(deployedProgram.stateHash(), 0);
assertEq(deployedProgram.inheritor(), address(0));

vm.roll(100);

Expand All @@ -93,7 +94,8 @@ contract RouterTest is Test {

IRouter.StateTransition[] memory transitionsArray = new IRouter.StateTransition[](1);
IRouter.BlockCommitment[] memory blockCommitmentsArray = new IRouter.BlockCommitment[](1);
transitionsArray[0] = IRouter.StateTransition(actorId, bytes32(uint256(1)), 0, valueClaims, outgoingMessages);
transitionsArray[0] =
IRouter.StateTransition(actorId, bytes32(uint256(1)), address(0), 0, valueClaims, outgoingMessages);
blockCommitmentsArray[0] = IRouter.BlockCommitment(
bytes32(uint256(1)), bytes32(uint256(0)), blockhash(block.number - 1), transitionsArray
);
Expand Down Expand Up @@ -174,6 +176,7 @@ contract RouterTest is Test {
abi.encodePacked(
transition.actorId,
transition.newStateHash,
transition.inheritor,
transition.valueToReceive,
keccak256(valueClaimsBytes),
keccak256(messagesHashesBytes)
Expand Down
2 changes: 1 addition & 1 deletion ethexe/ethereum/Mirror.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ethexe/ethereum/MirrorProxy.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ethexe/ethereum/Router.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ethexe/ethereum/WrappedVara.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions ethexe/ethereum/src/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ impl From<router::StateTransition> for IRouter::StateTransition {
router::StateTransition {
actor_id,
new_state_hash,
inheritor,
value_to_receive,
value_claims,
messages,
Expand All @@ -101,6 +102,7 @@ impl From<router::StateTransition> for IRouter::StateTransition {
Self {
actorId: actor_id.to_address_lossy().to_fixed_bytes().into(),
newStateHash: new_state_hash.to_fixed_bytes().into(),
inheritor: inheritor.to_address_lossy().to_fixed_bytes().into(),
valueToReceive: value_to_receive,
valueClaims: value_claims.into_iter().map(Into::into).collect(),
messages: messages.into_iter().map(Into::into).collect(),
Expand Down
10 changes: 8 additions & 2 deletions ethexe/processor/src/handling/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,10 @@ impl Processor {
self.handle_reply_queueing(state_hash, replied_to, source, payload, value)?
{
in_block_transitions
.modify_state_with(actor_id, new_state_hash, 0, vec![value_claim], vec![])
.modify_transition(actor_id, |state_hash, transition| {
*state_hash = new_state_hash;
transition.claims.push(value_claim);
})
.ok_or_else(|| anyhow!("failed to modify state of recognized program"))?;

in_block_transitions.remove_task(
Expand All @@ -135,7 +138,10 @@ impl Processor {
self.handle_value_claiming(state_hash, claimed_id, source)?
{
in_block_transitions
.modify_state_with(actor_id, new_state_hash, 0, vec![value_claim], vec![])
.modify_transition(actor_id, |state_hash, transition| {
*state_hash = new_state_hash;
transition.claims.push(value_claim);
})
.ok_or_else(|| anyhow!("failed to modify state of recognized program"))?;

in_block_transitions.remove_task(
Expand Down
47 changes: 31 additions & 16 deletions ethexe/runtime/common/src/journal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
};
use alloc::{collections::BTreeMap, vec, vec::Vec};
use anyhow::{bail, Result};
use core::num::NonZero;
use core::{mem, num::NonZero};
use core_processor::{
common::{DispatchOutcome, JournalHandler},
configs::BlockInfo,
Expand Down Expand Up @@ -126,11 +126,31 @@ impl<S: Storage> JournalHandler for Handler<'_, S> {
}

fn exit_dispatch(&mut self, id_exited: ProgramId, value_destination: ProgramId) {
// TODO (breathx): upd contract on exit and send value.
// TODO (breathx): handle rest of value cases; exec balance into value_to_receive.
let mut balance = 0;

self.update_state(id_exited, |state| {
state.program = Program::Exited(value_destination);
balance = mem::replace(&mut state.balance, 0);
Ok(())
});

if self
.in_block_transitions
.state_of(&value_destination)
.is_some()
{
self.update_state(value_destination, |state| {
state.balance += balance;
Ok(())
});
}

self.in_block_transitions
.modify_transition(id_exited, |_state_hash, transition| {
transition.inheritor = value_destination
})
.expect("infallible");
}

fn message_consumed(&mut self, message_id: MessageId) {
Expand Down Expand Up @@ -218,19 +238,12 @@ impl<S: Storage> JournalHandler for Handler<'_, S> {
let source = dispatch.source();
let message = dispatch.into_parts().1;

let state_hash = self
.in_block_transitions
.state_of(&source)
self.in_block_transitions
.modify_transition(source, |_state_hash, transition| {
transition.messages.push(OutgoingMessage::from(message))
})
.expect("must exist");

self.in_block_transitions.modify_state_with(
source,
state_hash,
0,
vec![],
vec![OutgoingMessage::from(message)],
);

return;
}

Expand Down Expand Up @@ -418,14 +431,16 @@ impl<S: Storage> JournalHandler for Handler<'_, S> {
return;
}

let state_hash = self.update_state(to, |state| {
self.update_state(to, |state| {
state.balance += value;
Ok(())
});

self.in_block_transitions
.modify_state_with(to, state_hash, value, vec![], vec![])
.expect("queried above; infallible");
.modify_transition(to, |_state_hash, transition| {
transition.value_to_receive += value
})
.expect("must exist");
}
}

Expand Down
3 changes: 0 additions & 3 deletions ethexe/runtime/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,8 @@ pub fn process_next_message<S: Storage, RI: RuntimeInterface<S>>(
// TBD about deprecation
SyscallName::SignalCode,
SyscallName::SignalFrom,
// TODO: refactor asap
SyscallName::GasAvailable,
// Temporary forbidden (unimplemented)
SyscallName::CreateProgram,
SyscallName::Exit,
SyscallName::Random,
]
.into(),
Expand Down
12 changes: 8 additions & 4 deletions ethexe/runtime/common/src/schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ impl<'a, S: Storage> TaskHandler<Rfm, Sd, Sum> for Handler<'a, S> {
) -> u64 {
let mut value_claim = None;

let state_hash = self.update_state_with_storage(program_id, |storage, state| {
self.update_state_with_storage(program_id, |storage, state| {
let ((claimed_value, expiry), new_mailbox_hash) = storage
.modify_mailbox_if_changed(state.mailbox_hash.clone(), |mailbox| {
let local_mailbox = mailbox.get_mut(&user_id)?;
Expand Down Expand Up @@ -81,7 +81,9 @@ impl<'a, S: Storage> TaskHandler<Rfm, Sd, Sum> for Handler<'a, S> {

if let Some(value_claim) = value_claim {
self.in_block_transitions
.modify_state_with(program_id, state_hash, 0, vec![value_claim], vec![])
.modify_transition(program_id, |_state_hash, transition| {
transition.claims.push(value_claim)
})
.expect("can't be None");
}

Expand Down Expand Up @@ -134,7 +136,7 @@ impl<'a, S: Storage> TaskHandler<Rfm, Sd, Sum> for Handler<'a, S> {
ScheduledTask::RemoveFromMailbox((program_id, user_id), stashed_message_id),
);

let state_hash = self.update_state_with_storage(program_id, |storage, state| {
self.update_state_with_storage(program_id, |storage, state| {
state.mailbox_hash =
storage.modify_mailbox(state.mailbox_hash.clone(), |mailbox| {
let r = mailbox
Expand All @@ -151,7 +153,9 @@ impl<'a, S: Storage> TaskHandler<Rfm, Sd, Sum> for Handler<'a, S> {
let outgoing_message = dispatch.into_outgoing(self.storage, user_id);

self.in_block_transitions
.modify_state_with(program_id, state_hash, 0, vec![], vec![outgoing_message])
.modify_transition(program_id, |_state_hash, transition| {
transition.messages.push(outgoing_message)
})
.expect("must be")
}

Expand Down
Loading

0 comments on commit 83a2880

Please sign in to comment.