<aside> ⚠️ This is a work-in-progress, a highly experimental contract, this should NOT be used in production or treated as a “valid” contract by any means.
</aside>
xAllowances
: Allows for a contract on a different chain to use your funds.remoteTransferFrom
: Allows a contract on chain A to send funds held in chain B to chain C with a message.pragma solidity 0.8.23;
contract OptimismSuperchainERC20 is ERC20 {
// TODO: discuss if order is the most clean/optimal
// fix naming and ordering of variables to match ERC20 standard (owner > spender > recipient)
mapping(address account => mapping(address spender => mapping(uint256 chainId => uint256 amount))) private
_xAllowances;
event xApproval(address indexed owner, address indexed spender, uint256 indexed chainId, uint256 amount);
event Relay(address indexed owner, address indexed recipient, address indexed spender, uint256 chainId, bytes data);
event RelayAndExecute(
address indexed owner, address indexed recipient, address indexed spender, uint256 originChainId, uint256 spenderChainId, uint256 amount, bytes data
);
event RelayTransferFrom(
address indexed owner,
address indexed recipient,
address indexed spender,
uint256 originChainId,
uint256 executeChainId,
uint256 amount,
bytes data
);
constructor(string memory _name, string memory _symbol, uint8 _decimals) ERC20(_name, _symbol, _decimals) {}
/*//////////////////////////////////////////////////
Functions for when funds are in initiating chain
////////////////////////////////////////////////////*/
function xTransfer(uint256 _amount, uint256 _chainId) external returns (bool success) {
success = xTransferTo(msg.sender, _amount, _chainId);
}
function xTransferTo(address _to, uint256 _amount, uint256 _chainId) public returns (bool) {
_burn(msg.sender, _amount);
bytes memory _message = abi.encodeCall(this.xReceive, (_to, _amount));
_sendMessage(_chainId, _message);
return true;
}
// xApproval should ONLY be called LOCALLY to be used on relayTransferFrom
// xApproval should be on the domain where the assets live.
function xApprove(address _spender, uint256 _chainId, uint256 _amount) external returns (bool) {
// TODO [research] _chainId wildcard as type(uint256).max (this is very risky)
_xAllowances[msg.sender][_spender][_chainId] = _amount;
emit xApproval(msg.sender, _spender, _chainId, _amount);
return true;
}
function allowance(address _owner, address _spender) external view returns (uint256) {
return allowance(_owner, _spender, block.chainid);
}
function allowance(address _owner, address _spender, uint256 _chainId) external view returns (uint256) {
if (_chainId == block.chainId) return _allowance[_owner][_spender];
return _xAllowance[_owner][_spender][_chainId];
}
/*/////////////////////////////////////////////
Functions for when funds are in remote chain
/////////////////////////////////////////////*/
function remoteTransferFrom(
address _owner,
address _recipient,
uint256 _spendChainId,
uint256 _executeChainId,
uint256 _amount,
bytes memory _data
) external returns (bool) {
bytes memory _message =
abi.encodeCall(this.relayTransferFrom, (_owner, msg.sender, _recipient, block.chainid, _executionChainId, _amount, _data));
_sendMessage(_spendChainId, _message);
return true;
}
/*/////////////////////////////////////////////
Functions to handle executing messages
/////////////////////////////////////////////*/
function xReceive(address _to, uint256 _amount) external OnlyPredeploys {
_mint(_to, _amount);
}
function relayTransferFrom(
address _owner,
address _spender,
address _recipient,
uint256 _originChainId,
uint256 _executionChainId,
uint256 _amount,
bytes memory _data
) external OnlyPredeploys {
uint256 allowed = _xAllowances[_owner][_spender][_originChainId];
if (allowed != type(uint256).max) {
_xAllowances[_owner][_spender][_originChainId] = allowed - _amount;
}
_burn(_owner, _amount);
if (block.chain == _executionChainId){
_relayAndExecute(_owner, _recipient, _spender, _originChainId, block.chainid, _amount, _data);
} else {
bytes memory _message =
abi.encodeCall(this.relayAndExecute, (_owner, _recipient, _spender, _originChainId, block.chainid, _amount, _data));
_sendMessage(_executionChainId, _message);
}
emit RelayTransferFrom(_owner, _recipient, _spender, _originChainId, _executeChainId, _amount, _data);
}
function relayAndExecute(
address _owner,
address _recipient,
address _spender,
uint256 _originChainId,
uint256 _spenderChainId,
uint256 _amount,
bytes memory _data
) external OnlyPredeploys {
_relayAndExecute(_owner, _recipient, _spender, _originChainId, _spenderChainId, _amount, _data);
}
function _relayAndExecute(
address _owner,
address _recipient,
address _spender,
uint256 _originChainId,
uint256 _spenderChainId,
uint256 _amount,
bytes memory _data
) external {
_mint(_recipient, _amount);
if (_data.length > 0) {
IxCallback(_spender).xCallback(_owner, _recipient, _spender, _originChainId, _spenderChainId, _amount, _data);
}
emit RelayAndExecute(_owner, _recipient, _spender, _originChainId, _spenderChainId, _amount, _data);
}
function _sendMessage(uint256 _destination, bytes memory _data) internal virtual {
// this assumes the token is deployed in the same address
// in every chain.
// TODO: check if this is possible
L2ToL2CrossDomainMessenger.sendMessage(_destination, address(this), _data);
}
// TODO: think of a better name for this
modifier OnlyPredeploys() virtual {
require(msg.sender == address(L2ToL2CrossChainMessenger));
// this matches above assumption, so TODO: check.
require(L2ToL2CrossChainMessenger.crossDomainMessageSender() == address(this));
_;
}
}