Commit 711c4a06 authored by smartcontracts's avatar smartcontracts Committed by GitHub

feat(contracts): deposit gas metering via 1559 (#2575)

* maint(ct): clean up OptimismPortal contract

Cleans up and standardizes the OptimismPortal contract.

* feat(ct): implement EIP-1559 gas burn

Implements the EIP-1559 gas burn in the OptimismPortal using Joshua's
specification. Simplified the initial draft by Mark quite a bit at the
expense of slightly increased gas costs.
Co-authored-by: default avatarmergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: default avatarMark Tyneway <mark.tyneway@gmail.com>
parent 16a9c704
...@@ -31,7 +31,7 @@ var ( ...@@ -31,7 +31,7 @@ var (
// L2ToL1MessagePasserMetaData contains all meta data concerning the L2ToL1MessagePasser contract. // L2ToL1MessagePasserMetaData contains all meta data concerning the L2ToL1MessagePasser contract.
var L2ToL1MessagePasserMetaData = &bind.MetaData{ var L2ToL1MessagePasserMetaData = &bind.MetaData{
ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"gasLimit\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"WithdrawalInitiated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"WithdrawerBalanceBurnt\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"burn\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_target\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_gasLimit\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"initiateWithdrawal\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"nonce\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"sentMessages\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]", ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"gasLimit\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"WithdrawalInitiated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"WithdrawerBalanceBurnt\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"burn\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_target\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_gasLimit\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"initiateWithdrawal\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"nonce\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"sentMessages\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]",
Bin: "0x608060405234801561001057600080fd5b506104a1806100206000396000f3fe6080604052600436106100435760003560e01c806344df8e701461006c57806382e3702d14610081578063affed0e0146100c6578063c2b3e5ac146100ea57600080fd5b366100675761006533620186a0604051806020016040528060008152506100f8565b005b600080fd5b34801561007857600080fd5b506100656101ae565b34801561008d57600080fd5b506100b161009c366004610256565b60006020819052908152604090205460ff1681565b60405190151581526020015b60405180910390f35b3480156100d257600080fd5b506100dc60015481565b6040519081526020016100bd565b6100656100f836600461029e565b600061010a600154338634878761020b565b6000818152602081905260409081902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600190811790915554905191925073ffffffffffffffffffffffffffffffffffffffff8616913391907f87bf7b546c8de873abb0db5b579ec131f8d0cf5b14f39933551cf9ced23a6136906101989034908990899061040d565b60405180910390a4505060018054810190555050565b604051479081906101be9061024a565b6040518091039082f09050801580156101db573d6000803e3d6000fd5b505060405181907f7967de617a5ac1cc7eba2d6f37570a0135afa950d8bb77cdd35f0d0b4e85a16f90600090a250565b600086868686868660405160200161022896959493929190610435565b6040516020818303038152906040528051906020012090509695505050505050565b60088061048d83390190565b60006020828403121561026857600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000806000606084860312156102b357600080fd5b833573ffffffffffffffffffffffffffffffffffffffff811681146102d757600080fd5b925060208401359150604084013567ffffffffffffffff808211156102fb57600080fd5b818601915086601f83011261030f57600080fd5b8135818111156103215761032161026f565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156103675761036761026f565b8160405282815289602084870101111561038057600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b6000815180845260005b818110156103c8576020818501810151868301820152016103ac565b818111156103da576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b83815282602082015260606040820152600061042c60608301846103a2565b95945050505050565b868152600073ffffffffffffffffffffffffffffffffffffffff808816602084015280871660408401525084606083015283608083015260c060a083015261048060c08301846103a2565b9897505050505050505056fe608060405230fffea164736f6c634300080a000a", Bin: "0x608060405234801561001057600080fd5b506104ab806100206000396000f3fe6080604052600436106100435760003560e01c806344df8e701461006c57806382e3702d14610081578063affed0e0146100c6578063c2b3e5ac146100ea57600080fd5b366100675761006533620186a0604051806020016040528060008152506100f8565b005b600080fd5b34801561007857600080fd5b506100656101ae565b34801561008d57600080fd5b506100b161009c366004610260565b60006020819052908152604090205460ff1681565b60405190151581526020015b60405180910390f35b3480156100d257600080fd5b506100dc60015481565b6040519081526020016100bd565b6100656100f83660046102a8565b600061010a60015433863487876101e6565b6000818152602081905260409081902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600190811790915554905191925073ffffffffffffffffffffffffffffffffffffffff8616913391907f87bf7b546c8de873abb0db5b579ec131f8d0cf5b14f39933551cf9ced23a61369061019890349089908990610417565b60405180910390a4505060018054810190555050565b476101b881610225565b60405181907f7967de617a5ac1cc7eba2d6f37570a0135afa950d8bb77cdd35f0d0b4e85a16f90600090a250565b60008686868686866040516020016102039695949392919061043f565b6040516020818303038152906040528051906020012090509695505050505050565b8060405161023290610254565b6040518091039082f090508015801561024f573d6000803e3d6000fd5b505050565b60088061049783390190565b60006020828403121561027257600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000806000606084860312156102bd57600080fd5b833573ffffffffffffffffffffffffffffffffffffffff811681146102e157600080fd5b925060208401359150604084013567ffffffffffffffff8082111561030557600080fd5b818601915086601f83011261031957600080fd5b81358181111561032b5761032b610279565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561037157610371610279565b8160405282815289602084870101111561038a57600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b6000815180845260005b818110156103d2576020818501810151868301820152016103b6565b818111156103e4576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b83815282602082015260606040820152600061043660608301846103ac565b95945050505050565b868152600073ffffffffffffffffffffffffffffffffffffffff808816602084015280871660408401525084606083015283608083015260c060a083015261048a60c08301846103ac565b9897505050505050505056fe608060405230fffea164736f6c634300080a000a",
} }
// L2ToL1MessagePasserABI is the input ABI used to generate the binding from. // L2ToL1MessagePasserABI is the input ABI used to generate the binding from.
......
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
// This file is a generated binding and any manual changes will be lost. // This file is a generated binding and any manual changes will be lost.
package bindings package bindings
var L2ToL1MessagePasserDeployedBin = "0x6080604052600436106100435760003560e01c806344df8e701461006c57806382e3702d14610081578063affed0e0146100c6578063c2b3e5ac146100ea57600080fd5b366100675761006533620186a0604051806020016040528060008152506100f8565b005b600080fd5b34801561007857600080fd5b506100656101ae565b34801561008d57600080fd5b506100b161009c366004610256565b60006020819052908152604090205460ff1681565b60405190151581526020015b60405180910390f35b3480156100d257600080fd5b506100dc60015481565b6040519081526020016100bd565b6100656100f836600461029e565b600061010a600154338634878761020b565b6000818152602081905260409081902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600190811790915554905191925073ffffffffffffffffffffffffffffffffffffffff8616913391907f87bf7b546c8de873abb0db5b579ec131f8d0cf5b14f39933551cf9ced23a6136906101989034908990899061040d565b60405180910390a4505060018054810190555050565b604051479081906101be9061024a565b6040518091039082f09050801580156101db573d6000803e3d6000fd5b505060405181907f7967de617a5ac1cc7eba2d6f37570a0135afa950d8bb77cdd35f0d0b4e85a16f90600090a250565b600086868686868660405160200161022896959493929190610435565b6040516020818303038152906040528051906020012090509695505050505050565b60088061048d83390190565b60006020828403121561026857600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000806000606084860312156102b357600080fd5b833573ffffffffffffffffffffffffffffffffffffffff811681146102d757600080fd5b925060208401359150604084013567ffffffffffffffff808211156102fb57600080fd5b818601915086601f83011261030f57600080fd5b8135818111156103215761032161026f565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156103675761036761026f565b8160405282815289602084870101111561038057600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b6000815180845260005b818110156103c8576020818501810151868301820152016103ac565b818111156103da576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b83815282602082015260606040820152600061042c60608301846103a2565b95945050505050565b868152600073ffffffffffffffffffffffffffffffffffffffff808816602084015280871660408401525084606083015283608083015260c060a083015261048060c08301846103a2565b9897505050505050505056fe608060405230fffea164736f6c634300080a000a" var L2ToL1MessagePasserDeployedBin = "0x6080604052600436106100435760003560e01c806344df8e701461006c57806382e3702d14610081578063affed0e0146100c6578063c2b3e5ac146100ea57600080fd5b366100675761006533620186a0604051806020016040528060008152506100f8565b005b600080fd5b34801561007857600080fd5b506100656101ae565b34801561008d57600080fd5b506100b161009c366004610260565b60006020819052908152604090205460ff1681565b60405190151581526020015b60405180910390f35b3480156100d257600080fd5b506100dc60015481565b6040519081526020016100bd565b6100656100f83660046102a8565b600061010a60015433863487876101e6565b6000818152602081905260409081902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600190811790915554905191925073ffffffffffffffffffffffffffffffffffffffff8616913391907f87bf7b546c8de873abb0db5b579ec131f8d0cf5b14f39933551cf9ced23a61369061019890349089908990610417565b60405180910390a4505060018054810190555050565b476101b881610225565b60405181907f7967de617a5ac1cc7eba2d6f37570a0135afa950d8bb77cdd35f0d0b4e85a16f90600090a250565b60008686868686866040516020016102039695949392919061043f565b6040516020818303038152906040528051906020012090509695505050505050565b8060405161023290610254565b6040518091039082f090508015801561024f573d6000803e3d6000fd5b505050565b60088061049783390190565b60006020828403121561027257600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000806000606084860312156102bd57600080fd5b833573ffffffffffffffffffffffffffffffffffffffff811681146102e157600080fd5b925060208401359150604084013567ffffffffffffffff8082111561030557600080fd5b818601915086601f83011261031957600080fd5b81358181111561032b5761032b610279565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561037157610371610279565b8160405282815289602084870101111561038a57600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b6000815180845260005b818110156103d2576020818501810151868301820152016103b6565b818111156103e4576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b83815282602082015260606040820152600061043660608301846103ac565b95945050505050565b868152600073ffffffffffffffffffffffffffffffffffffffff808816602084015280871660408401525084606083015283608083015260c060a083015261048a60c08301846103ac565b9897505050505050505056fe608060405230fffea164736f6c634300080a000a"
This diff is collapsed.
...@@ -41,7 +41,7 @@ func WaitForFinalizationPeriod(ctx context.Context, client *ethclient.Client, po ...@@ -41,7 +41,7 @@ func WaitForFinalizationPeriod(ctx context.Context, client *ethclient.Client, po
return 0, err return 0, err
} }
finalizationPeriod, err := portal.FINALIZATIONPERIOD(opts) finalizationPeriod, err := portal.FINALIZATIONPERIODSECONDS(opts)
if err != nil { if err != nil {
return 0, err return 0, err
} }
......
...@@ -26,17 +26,17 @@ L1BlockNumberTest:test_receive() (gas: 17418) ...@@ -26,17 +26,17 @@ L1BlockNumberTest:test_receive() (gas: 17418)
L1CrossDomainMessenger_Test:testCannot_L1MessengerPause() (gas: 10909) L1CrossDomainMessenger_Test:testCannot_L1MessengerPause() (gas: 10909)
L1CrossDomainMessenger_Test:test_L1MessengerMessageVersion() (gas: 8366) L1CrossDomainMessenger_Test:test_L1MessengerMessageVersion() (gas: 8366)
L1CrossDomainMessenger_Test:test_L1MessengerPause() (gas: 31882) L1CrossDomainMessenger_Test:test_L1MessengerPause() (gas: 31882)
L1CrossDomainMessenger_Test:test_L1MessengerRelayMessageSucceeds() (gas: 61129) L1CrossDomainMessenger_Test:test_L1MessengerRelayMessageSucceeds() (gas: 61175)
L1CrossDomainMessenger_Test:test_L1MessengerRelayMessageToSystemContract() (gas: 44815) L1CrossDomainMessenger_Test:test_L1MessengerRelayMessageToSystemContract() (gas: 44859)
L1CrossDomainMessenger_Test:test_L1MessengerRelayShouldRevertIfPaused() (gas: 41631) L1CrossDomainMessenger_Test:test_L1MessengerRelayShouldRevertIfPaused() (gas: 41631)
L1CrossDomainMessenger_Test:test_L1MessengerSendMessage() (gas: 78105) L1CrossDomainMessenger_Test:test_L1MessengerSendMessage() (gas: 172148)
L1CrossDomainMessenger_Test:test_L1MessengerTwiceSendMessage() (gas: 66345) L1CrossDomainMessenger_Test:test_L1MessengerTwiceSendMessage() (gas: 1254131)
L1CrossDomainMessenger_Test:test_L1MessengerXDomainSenderReverts() (gas: 10566) L1CrossDomainMessenger_Test:test_L1MessengerXDomainSenderReverts() (gas: 10566)
L1CrossDomainMessenger_Test:test_L1MessengerxDomainMessageSenderResets() (gas: 58425) L1CrossDomainMessenger_Test:test_L1MessengerxDomainMessageSenderResets() (gas: 58471)
L1StandardBridge_Test:test_depositERC20() (gas: 371479) L1StandardBridge_Test:test_depositERC20() (gas: 452873)
L1StandardBridge_Test:test_depositERC20To() (gas: 373256) L1StandardBridge_Test:test_depositERC20To() (gas: 454650)
L1StandardBridge_Test:test_depositETH() (gas: 105061) L1StandardBridge_Test:test_depositETH() (gas: 247054)
L1StandardBridge_Test:test_depositETHTo() (gas: 111945) L1StandardBridge_Test:test_depositETHTo() (gas: 204938)
L1StandardBridge_Test:test_donateETH() (gas: 17545) L1StandardBridge_Test:test_donateETH() (gas: 17545)
L1StandardBridge_Test:test_finalizeERC20Withdrawal() (gas: 438817) L1StandardBridge_Test:test_finalizeERC20Withdrawal() (gas: 438817)
L1StandardBridge_Test:test_finalizeETHWithdrawal() (gas: 48005) L1StandardBridge_Test:test_finalizeETHWithdrawal() (gas: 48005)
...@@ -45,7 +45,7 @@ L1StandardBridge_Test:test_onlyEOADepositERC20() (gas: 12085) ...@@ -45,7 +45,7 @@ L1StandardBridge_Test:test_onlyEOADepositERC20() (gas: 12085)
L1StandardBridge_Test:test_onlyEOADepositETH() (gas: 30637) L1StandardBridge_Test:test_onlyEOADepositETH() (gas: 30637)
L1StandardBridge_Test:test_onlyL2BridgeFinalizeERC20Withdrawal() (gas: 23565) L1StandardBridge_Test:test_onlyL2BridgeFinalizeERC20Withdrawal() (gas: 23565)
L1StandardBridge_Test:test_onlyPortalFinalizeERC20Withdrawal() (gas: 22919) L1StandardBridge_Test:test_onlyPortalFinalizeERC20Withdrawal() (gas: 22919)
L1StandardBridge_Test:test_receive() (gas: 99476) L1StandardBridge_Test:test_receive() (gas: 391794)
L2CrossDomainMessenger_Test:testCannot_L2MessengerPause() (gas: 10843) L2CrossDomainMessenger_Test:testCannot_L2MessengerPause() (gas: 10843)
L2CrossDomainMessenger_Test:test_L2MessengerMessageVersion() (gas: 8410) L2CrossDomainMessenger_Test:test_L2MessengerMessageVersion() (gas: 8410)
L2CrossDomainMessenger_Test:test_L2MessengerPause() (gas: 31837) L2CrossDomainMessenger_Test:test_L2MessengerPause() (gas: 31837)
...@@ -76,7 +76,7 @@ L2StandardBridge_Test:test_initialize() (gas: 14834) ...@@ -76,7 +76,7 @@ L2StandardBridge_Test:test_initialize() (gas: 14834)
L2StandardBridge_Test:test_receive() (gas: 136437) L2StandardBridge_Test:test_receive() (gas: 136437)
L2StandardBridge_Test:test_withdraw() (gas: 352644) L2StandardBridge_Test:test_withdraw() (gas: 352644)
L2StandardBridge_Test:test_withdrawTo() (gas: 353495) L2StandardBridge_Test:test_withdrawTo() (gas: 353495)
L2ToL1MessagePasserTest:test_burn() (gas: 112023) L2ToL1MessagePasserTest:test_burn() (gas: 112046)
L2ToL1MessagePasserTest:test_initiateWithdrawal_fromContract() (gas: 67890) L2ToL1MessagePasserTest:test_initiateWithdrawal_fromContract() (gas: 67890)
L2ToL1MessagePasserTest:test_initiateWithdrawal_fromEOA() (gas: 74851) L2ToL1MessagePasserTest:test_initiateWithdrawal_fromEOA() (gas: 74851)
OVM_ETH_Test:test_approve() (gas: 10695) OVM_ETH_Test:test_approve() (gas: 10695)
...@@ -101,19 +101,27 @@ OptimismMintableTokenFactory_Test:test_createStandardL2Token() (gas: 1106538) ...@@ -101,19 +101,27 @@ OptimismMintableTokenFactory_Test:test_createStandardL2Token() (gas: 1106538)
OptimismMintableTokenFactory_Test:test_createStandardL2TokenSameTwice() (gas: 2193987) OptimismMintableTokenFactory_Test:test_createStandardL2TokenSameTwice() (gas: 2193987)
OptimismMintableTokenFactory_Test:test_createStandardL2TokenShouldRevertIfRemoteIsZero() (gas: 9374) OptimismMintableTokenFactory_Test:test_createStandardL2TokenShouldRevertIfRemoteIsZero() (gas: 9374)
OptimismMintableTokenFactory_Test:test_initializeShouldRevert() (gas: 12696) OptimismMintableTokenFactory_Test:test_initializeShouldRevert() (gas: 12696)
OptimismPortal_Test:test_OptimismPortalConstructor() (gas: 11302) OptimismPortal_Test:test_OptimismPortalConstructor() (gas: 11413)
OptimismPortal_Test:test_OptimismPortalContractCreationReverts() (gas: 9421) OptimismPortal_Test:test_OptimismPortalContractCreationReverts() (gas: 9214)
OptimismPortal_Test:test_OptimismPortalReceiveEth() (gas: 24732) OptimismPortal_Test:test_OptimismPortalReceiveEth() (gas: 121706)
OptimismPortal_Test:test_cannotVerifyRecentWithdrawal() (gas: 19657) OptimismPortal_Test:test_cannotVerifyRecentWithdrawal() (gas: 21863)
OptimismPortal_Test:test_depositTransaction_NoValueContract() (gas: 24478) OptimismPortal_Test:test_depositTransaction_NoValueContract() (gas: 70746)
OptimismPortal_Test:test_depositTransaction_NoValueEOA() (gas: 24846) OptimismPortal_Test:test_depositTransaction_NoValueEOA() (gas: 71114)
OptimismPortal_Test:test_depositTransaction_createWithZeroValueForContract() (gas: 24497) OptimismPortal_Test:test_depositTransaction_createWithZeroValueForContract() (gas: 70773)
OptimismPortal_Test:test_depositTransaction_createWithZeroValueForEOA() (gas: 24841) OptimismPortal_Test:test_depositTransaction_createWithZeroValueForEOA() (gas: 71117)
OptimismPortal_Test:test_depositTransaction_withEthValueAndContractContractCreation() (gas: 31519) OptimismPortal_Test:test_depositTransaction_withEthValueAndContractContractCreation() (gas: 77795)
OptimismPortal_Test:test_depositTransaction_withEthValueAndEOAContractCreation() (gas: 22971) OptimismPortal_Test:test_depositTransaction_withEthValueAndEOAContractCreation() (gas: 69947)
OptimismPortal_Test:test_depositTransaction_withEthValueFromContract() (gas: 31210) OptimismPortal_Test:test_depositTransaction_withEthValueFromContract() (gas: 77478)
OptimismPortal_Test:test_depositTransaction_withEthValueFromEOA() (gas: 31781) OptimismPortal_Test:test_depositTransaction_withEthValueFromEOA() (gas: 78049)
OptimismPortal_Test:test_invalidWithdrawalProof() (gas: 26565) OptimismPortal_Test:test_invalidWithdrawalProof() (gas: 28541)
ResourceMetering_Test:test_initialResourceParams() (gas: 8986)
ResourceMetering_Test:test_updateNoGasDelta() (gas: 2008269)
ResourceMetering_Test:test_updateOneEmptyBlock() (gas: 18078)
ResourceMetering_Test:test_updateParamsNoChange() (gas: 13860)
ResourceMetering_Test:test_updateTenEmptyBlocks() (gas: 20545)
ResourceMetering_Test:test_updateTwoEmptyBlocks() (gas: 20546)
ResourceMetering_Test:test_useMaxSucceeds() (gas: 8017045)
ResourceMetering_Test:test_useMoreThanMaxReverts() (gas: 16024)
SequencerFeeVault_Test:test_constructor() (gas: 7611) SequencerFeeVault_Test:test_constructor() (gas: 7611)
SequencerFeeVault_Test:test_minWithdrawalAmount() (gas: 5429) SequencerFeeVault_Test:test_minWithdrawalAmount() (gas: 5429)
SequencerFeeVault_Test:test_receive() (gas: 17280) SequencerFeeVault_Test:test_receive() (gas: 17280)
......
# @eth-optimism/contracts-bedrock # @eth-optimism/contracts-bedrock
## 0.1.1 ## 0.1.1
### Patch Changes ### Patch Changes
- 1aca58c4: Initial release - 1aca58c4: Initial release
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { SignedMath } from "@openzeppelin/contracts/utils/math/SignedMath.sol";
import { FixedPointMathLib } from "@rari-capital/solmate/src/utils/FixedPointMathLib.sol";
import { Burn } from "../libraries/Burn.sol";
/**
* @title ResourceMetering
* @notice ResourceMetering implements an EIP-1559 style resource metering system where pricing
* updates automatically based on current demand.
*/
contract ResourceMetering {
/**
* Struct representing current resource parameters.
*/
struct ResourceParams {
uint128 prevBaseFee;
uint64 prevBoughtGas;
uint64 prevBlockNum;
}
/**
* Along with the resource limit, determines the target resource limit.
*/
int256 public constant ELASTICITY_MULTIPLIER = 4;
/**
* Denominator that determines max change on fee per block.
*/
int256 public constant BASE_FEE_MAX_CHANGE_DENOMINATOR = 8;
/**
* Maximum amount of deposit gas that can be used within this block.
*/
int256 public constant MAX_RESOURCE_LIMIT = 8_000_000;
/**
* Target amount of deposit gas that should be used within this block.
*/
int256 public constant TARGET_RESOURCE_LIMIT = MAX_RESOURCE_LIMIT / ELASTICITY_MULTIPLIER;
/**
* Minimum base fee value, cannot go lower than this.
*/
int256 public constant MINIMUM_BASE_FEE = 10_000;
/**
* Initial base fee value.
*/
uint128 public constant INITIAL_BASE_FEE = 1_000_000_000;
/**
* EIP-1559 style gas parameters.
*/
ResourceParams public params;
/**
* Sets the initial resource values.
*/
constructor() {
params = ResourceParams({
prevBaseFee: INITIAL_BASE_FEE,
prevBoughtGas: 0,
prevBlockNum: uint64(block.number)
});
}
/**
* Meters access to a function based an amount of a requested resource.
*
* @param _amount Amount of the resource requested.
*/
modifier metered(uint64 _amount) {
// Record initial gas amount so we can refund for it later.
uint256 initialGas = gasleft();
// Run the underlying function.
_;
// Update block number and base fee if necessary.
uint256 blockDiff = block.number - params.prevBlockNum;
if (blockDiff > 0) {
// Handle updating EIP-1559 style gas parameters. We use EIP-1559 to restrict the rate
// at which deposits can be created and therefore limit the potential for deposits to
// spam the L2 system. Fee scheme is very similar to EIP-1559 with minor changes.
int256 gasUsedDelta = int256(uint256(params.prevBoughtGas)) - TARGET_RESOURCE_LIMIT;
int256 baseFeeDelta = (int256(uint256(params.prevBaseFee)) * gasUsedDelta) /
TARGET_RESOURCE_LIMIT /
BASE_FEE_MAX_CHANGE_DENOMINATOR;
// Update base fee by adding the base fee delta and clamp the resulting value between
// min and max.
int256 newBaseFee = SignedMath.min(
SignedMath.max(
int256(uint256(params.prevBaseFee)) + baseFeeDelta,
int256(MINIMUM_BASE_FEE)
),
int256(uint256(type(uint128).max))
);
// If we skipped more than one block, we also need to account for every empty block.
// Empty block means there was no demand for deposits in that block, so we should
// reflect this lack of demand in the fee.
if (blockDiff > 1) {
// Update the base fee by repeatedly applying the exponent 1-(1/change_denominator)
// blockDiff - 1 times. Simulates multiple empty blocks. Clamp the resulting value
// between min and max.
newBaseFee = SignedMath.min(
SignedMath.max(
int256(
(newBaseFee *
(
FixedPointMathLib.powWad(
1e18 - (1e18 / BASE_FEE_MAX_CHANGE_DENOMINATOR),
int256((blockDiff - 1) * 1e18)
)
)) / 1e18
),
int256(MINIMUM_BASE_FEE)
),
int256(uint256(type(uint128).max))
);
}
// Update new base fee, reset bought gas, and update block number.
params.prevBaseFee = uint128(uint256(newBaseFee));
params.prevBoughtGas = 0;
params.prevBlockNum = uint64(block.number);
}
// Make sure we can actually buy the resource amount requested by the user.
params.prevBoughtGas += _amount;
require(
int256(uint256(params.prevBoughtGas)) <= MAX_RESOURCE_LIMIT,
"OptimismPortal: cannot buy more gas than available gas limit"
);
// Determine the amount of ETH to be paid.
uint256 resourceCost = _amount * params.prevBaseFee;
// We currently charge for this ETH amount as an L1 gas burn, so we convert the ETH amount
// into gas by dividing by the L1 base fee. We assume a minimum base fee of 1 gwei to avoid
// division by zero for L1s that don't support 1559 or to avoid excessive gas burns during
// periods of extremely low L1 demand. One-day average gas fee hasn't dipped below 1 gwei
// during any 1 day period in the last 5 years, so should be fine.
uint256 gasCost = resourceCost / Math.max(block.basefee, 1000000000);
// Give the user a refund based on the amount of gas they used to do all of the work up to
// this point. Since we're at the end of the modifier, this should be pretty accurate. Acts
// effectively like a dynamic stipend (with a minimum value).
uint256 usedGas = initialGas - gasleft();
if (gasCost > usedGas) {
Burn.gas(gasCost - usedGas);
}
}
}
//SPDX-License-Identifier: MIT
pragma solidity 0.8.10;
/**
* @title Burner
* @dev This contract is used to remove ETH from
* the L2 circulating supply as it is withdrawn.
*/
contract Burner {
constructor() payable {
selfdestruct(payable(address(this)));
}
}
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
pragma solidity ^0.8.9; pragma solidity ^0.8.9;
import { WithdrawalVerifier } from "../libraries/Lib_WithdrawalVerifier.sol"; import { WithdrawalVerifier } from "../libraries/Lib_WithdrawalVerifier.sol";
import { Burner } from "./Burner.sol"; import { Burn } from "../libraries/Burn.sol";
/** /**
* @title L2ToL1MessagePasser * @title L2ToL1MessagePasser
...@@ -101,7 +101,7 @@ contract L2ToL1MessagePasser { ...@@ -101,7 +101,7 @@ contract L2ToL1MessagePasser {
*/ */
function burn() external { function burn() external {
uint256 balance = address(this).balance; uint256 balance = address(this).balance;
new Burner{ value: balance }(); Burn.eth(balance);
emit WithdrawerBalanceBurnt(balance); emit WithdrawerBalanceBurnt(balance);
} }
} }
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;
/**
* @title Burner
* @notice Burner self-destructs on creation and sends all ETH to itself, removing all ETH given to
* the contract from the circulating supply.
*/
contract Burner {
constructor() payable {
selfdestruct(payable(address(this)));
}
}
/**
* @title Burn
* @notice Utilities for burning stuff.
*/
library Burn {
/**
* Burns a given amount of ETH.
*
* @param _amount Amount of ETH to burn.
*/
function eth(uint256 _amount) internal {
new Burner{ value: _amount }();
}
/**
* Burns a given amount of gas.
*
* @param _amount Amount of gas to burn.
*/
function gas(uint256 _amount) internal {
uint256 i = 0;
uint256 initialGas = gasleft();
while (initialGas - gasleft() < _amount) {
++i;
}
}
}
...@@ -37,6 +37,9 @@ contract CommonTest is Test { ...@@ -37,6 +37,9 @@ contract CommonTest is Test {
vm.label(alice, "alice"); vm.label(alice, "alice");
vm.label(bob, "bob"); vm.label(bob, "bob");
// Make sure we have a non-zero base fee
vm.fee(1000000000);
} }
} }
contract L2OutputOracle_Initializer is CommonTest { contract L2OutputOracle_Initializer is CommonTest {
......
...@@ -136,7 +136,8 @@ contract L1CrossDomainMessenger_Test is Messenger_Initializer { ...@@ -136,7 +136,8 @@ contract L1CrossDomainMessenger_Test is Messenger_Initializer {
vm.expectCall(target, hex"1111"); vm.expectCall(target, hex"1111");
// set the value of op.l2Sender() to be the L2 Cross Domain Messenger. // set the value of op.l2Sender() to be the L2 Cross Domain Messenger.
vm.store(address(op), 0, bytes32(abi.encode(sender))); uint256 senderSlotIndex = 1;
vm.store(address(op), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.prank(address(op)); vm.prank(address(op));
vm.expectEmit(true, true, true, true); vm.expectEmit(true, true, true, true);
...@@ -190,7 +191,8 @@ contract L1CrossDomainMessenger_Test is Messenger_Initializer { ...@@ -190,7 +191,8 @@ contract L1CrossDomainMessenger_Test is Messenger_Initializer {
L1Messenger.xDomainMessageSender(); L1Messenger.xDomainMessageSender();
address sender = Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER; address sender = Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER;
vm.store(address(op), 0, bytes32(abi.encode(sender))); uint256 senderSlotIndex = 1;
vm.store(address(op), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.prank(address(op)); vm.prank(address(op));
L1Messenger.relayMessage(0, address(0), address(0), 0, 0, hex""); L1Messenger.relayMessage(0, address(0), address(0), 0, 0, hex"");
......
...@@ -24,6 +24,7 @@ contract OptimismPortal_Test is CommonTest { ...@@ -24,6 +24,7 @@ contract OptimismPortal_Test is CommonTest {
OptimismPortal op; OptimismPortal op;
function setUp() external { function setUp() external {
_setUp();
oracle = new L2OutputOracle( oracle = new L2OutputOracle(
1800, 1800,
2, 2,
...@@ -36,7 +37,7 @@ contract OptimismPortal_Test is CommonTest { ...@@ -36,7 +37,7 @@ contract OptimismPortal_Test is CommonTest {
} }
function test_OptimismPortalConstructor() external { function test_OptimismPortalConstructor() external {
assertEq(op.FINALIZATION_PERIOD(), 7 days); assertEq(op.FINALIZATION_PERIOD_SECONDS(), 7 days);
assertEq(address(op.L2_ORACLE()), address(oracle)); assertEq(address(op.L2_ORACLE()), address(oracle));
assertEq(op.l2Sender(), 0x000000000000000000000000000000000000dEaD); assertEq(op.l2Sender(), 0x000000000000000000000000000000000000dEaD);
} }
...@@ -67,7 +68,7 @@ contract OptimismPortal_Test is CommonTest { ...@@ -67,7 +68,7 @@ contract OptimismPortal_Test is CommonTest {
// Test: depositTransaction fails when contract creation has a non-zero destination address // Test: depositTransaction fails when contract creation has a non-zero destination address
function test_OptimismPortalContractCreationReverts() external { function test_OptimismPortalContractCreationReverts() external {
// contract creation must have a target of address(0) // contract creation must have a target of address(0)
vm.expectRevert(abi.encodeWithSignature("NonZeroCreationTarget()")); vm.expectRevert("OptimismPortal: must send to address(0) when creating a contract");
op.depositTransaction(address(1), 1, 0, true, hex""); op.depositTransaction(address(1), 1, 0, true, hex"");
} }
...@@ -260,7 +261,7 @@ contract OptimismPortal_Test is CommonTest { ...@@ -260,7 +261,7 @@ contract OptimismPortal_Test is CommonTest {
latestBlockhash: bytes32(0) latestBlockhash: bytes32(0)
}); });
vm.expectRevert("Proposal is not yet finalized."); vm.expectRevert("OptimismPortal: proposal is not yet finalized");
op.finalizeWithdrawalTransaction( op.finalizeWithdrawalTransaction(
0, 0,
alice, alice,
...@@ -282,8 +283,8 @@ contract OptimismPortal_Test is CommonTest { ...@@ -282,8 +283,8 @@ contract OptimismPortal_Test is CommonTest {
latestBlockhash: bytes32(0) latestBlockhash: bytes32(0)
}); });
vm.warp(oracle.nextTimestamp() + op.FINALIZATION_PERIOD()); vm.warp(oracle.nextTimestamp() + op.FINALIZATION_PERIOD_SECONDS());
vm.expectRevert(abi.encodeWithSignature("InvalidOutputRootProof()")); vm.expectRevert("OptimismPortal: invalid output root proof");
op.finalizeWithdrawalTransaction( op.finalizeWithdrawalTransaction(
0, 0,
alice, alice,
......
//SPDX-License-Identifier: MIT
pragma solidity 0.8.10;
import { CommonTest } from "./CommonTest.t.sol";
import { ResourceMetering } from "../L1/ResourceMetering.sol";
contract MeterUser is ResourceMetering {
function use(uint64 _amount) public metered(_amount) {}
}
contract ResourceMetering_Test is CommonTest {
MeterUser internal meter;
uint64 initialBlockNum;
function setUp() external {
_setUp();
meter = new MeterUser();
initialBlockNum = uint64(block.number);
}
function test_initialResourceParams() external {
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
assertEq(prevBaseFee, meter.INITIAL_BASE_FEE());
assertEq(prevBoughtGas, 0);
assertEq(prevBlockNum, initialBlockNum);
}
function test_updateParamsNoChange() external {
meter.use(0); // equivalent to just updating the base fee and block number
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
meter.use(0);
(uint128 postBaseFee, uint64 postBoughtGas, uint64 postBlockNum) = meter.params();
assertEq(postBaseFee, prevBaseFee);
assertEq(postBoughtGas, prevBoughtGas);
assertEq(postBlockNum, prevBlockNum);
}
function test_updateOneEmptyBlock() external {
vm.roll(initialBlockNum + 1);
meter.use(0);
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
// Base fee decreases by 12.5%
assertEq(prevBaseFee, 875000000);
assertEq(prevBoughtGas, 0);
assertEq(prevBlockNum, initialBlockNum + 1);
}
function test_updateTwoEmptyBlocks() external {
vm.roll(initialBlockNum + 2);
meter.use(0);
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
assertEq(prevBaseFee, 765624999);
assertEq(prevBoughtGas, 0);
assertEq(prevBlockNum, initialBlockNum + 2);
}
function test_updateTenEmptyBlocks() external {
vm.roll(initialBlockNum + 10);
meter.use(0);
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
assertEq(prevBaseFee, 263075576);
assertEq(prevBoughtGas, 0);
assertEq(prevBlockNum, initialBlockNum + 10);
}
function test_updateNoGasDelta() external {
uint64 target = uint64(uint256(meter.TARGET_RESOURCE_LIMIT()));
meter.use(target);
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
assertEq(prevBaseFee, 1000000000);
assertEq(prevBoughtGas, target);
assertEq(prevBlockNum, initialBlockNum);
}
function test_useMaxSucceeds() external {
uint64 target = uint64(uint256(meter.TARGET_RESOURCE_LIMIT()));
uint64 elasticity = uint64(uint256(meter.ELASTICITY_MULTIPLIER()));
meter.use(target * elasticity);
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
assertEq(prevBoughtGas, target * elasticity);
vm.roll(initialBlockNum + 1);
meter.use(0);
(uint128 postBaseFee, uint64 postBoughtGas, uint64 postBlockNum) = meter.params();
// Base fee increases by 1/8 the difference
assertEq(postBaseFee, 1375000000);
}
function test_useMoreThanMaxReverts() external {
uint64 target = uint64(uint256(meter.TARGET_RESOURCE_LIMIT()));
uint64 elasticity = uint64(uint256(meter.ELASTICITY_MULTIPLIER()));
vm.expectRevert("OptimismPortal: cannot buy more gas than available gas limit");
meter.use(target * elasticity + 1);
}
}
...@@ -12,18 +12,6 @@ ...@@ -12,18 +12,6 @@
"artifacts/src/**/*.json", "artifacts/src/**/*.json",
"deployments/**/*.json" "deployments/**/*.json"
], ],
"dependencies": {
"@eth-optimism/core-utils": "^0.8.6",
"@openzeppelin/contracts": "^4.5.0",
"@openzeppelin/contracts-upgradeable": "^4.5.2",
"ethers": "^5.6.8",
"hardhat": "^2.9.6",
"@rari-capital/solmate": "7.0.0-alpha.1",
"ds-test": "https://github.com/dapphub/ds-test.git#9310e879db8ba3ea6d5c6489a579118fd264a3f5",
"forge-std": "https://github.com/foundry-rs/forge-std.git#1680d7fb3e00b7b197a7336e7c88e838c7e6a3ec",
"merkle-patricia-tree": "^4.2.4",
"rlp": "^2.2.7"
},
"scripts": { "scripts": {
"build:forge": "forge build", "build:forge": "forge build",
"build": "hardhat compile && tsc && hardhat typechain", "build": "hardhat compile && tsc && hardhat typechain",
...@@ -40,6 +28,18 @@ ...@@ -40,6 +28,18 @@
"lint:fix": "yarn lint:contracts:fix && yarn lint:ts:fix", "lint:fix": "yarn lint:contracts:fix && yarn lint:ts:fix",
"lint": "yarn lint:fix && yarn lint:check" "lint": "yarn lint:fix && yarn lint:check"
}, },
"dependencies": {
"@eth-optimism/core-utils": "^0.8.6",
"@openzeppelin/contracts": "^4.5.0",
"@openzeppelin/contracts-upgradeable": "^4.5.2",
"ethers": "^5.6.8",
"hardhat": "^2.9.6",
"@rari-capital/solmate": "https://github.com/rari-capital/solmate.git#8f9b23f8838670afda0fd8983f2c41e8037ae6bc",
"ds-test": "https://github.com/dapphub/ds-test.git#9310e879db8ba3ea6d5c6489a579118fd264a3f5",
"forge-std": "https://github.com/foundry-rs/forge-std.git#1680d7fb3e00b7b197a7336e7c88e838c7e6a3ec",
"merkle-patricia-tree": "^4.2.4",
"rlp": "^2.2.7"
},
"devDependencies": { "devDependencies": {
"@foundry-rs/hardhat-forge": "^0.1.5", "@foundry-rs/hardhat-forge": "^0.1.5",
"@nomiclabs/hardhat-ethers": "^2.0.0", "@nomiclabs/hardhat-ethers": "^2.0.0",
......
...@@ -5,7 +5,5 @@ ...@@ -5,7 +5,5 @@
"outDir": "./dist" "outDir": "./dist"
}, },
"exclude": ["hardhat.config.ts", "deploy", "tasks", "test"], "exclude": ["hardhat.config.ts", "deploy", "tasks", "test"],
"include": [ "include": ["src/**/*"]
"src/**/*"
]
} }
...@@ -2834,16 +2834,15 @@ ...@@ -2834,16 +2834,15 @@
dependencies: dependencies:
squirrelly "^8.0.8" squirrelly "^8.0.8"
"@rari-capital/solmate@7.0.0-alpha.1":
version "7.0.0-alpha.1"
resolved "https://registry.yarnpkg.com/@rari-capital/solmate/-/solmate-7.0.0-alpha.1.tgz#d5defd0489f0bc4bb8b15022752233e473fa5e3c"
integrity sha512-BS88U+DKcP6kpvxpIzKOKLlzJlJJBAKJbQNgmb3o/pRALbIAb2IT686cmWSCI5Kl13as6In8HnVxq92r418haw==
"@rari-capital/solmate@^6.3.0": "@rari-capital/solmate@^6.3.0":
version "6.3.0" version "6.3.0"
resolved "https://registry.yarnpkg.com/@rari-capital/solmate/-/solmate-6.3.0.tgz#01050e276e71dc4cd4169cf8002b447eb07d90c3" resolved "https://registry.yarnpkg.com/@rari-capital/solmate/-/solmate-6.3.0.tgz#01050e276e71dc4cd4169cf8002b447eb07d90c3"
integrity sha512-SWPbnfZUCe4ahHNqcb0qsPrzzAzMZMoA3x6SxZn04g0dLm0xupVeHonM3LK13uhPGIULF8HzXg8CgXE/fEnMlQ== integrity sha512-SWPbnfZUCe4ahHNqcb0qsPrzzAzMZMoA3x6SxZn04g0dLm0xupVeHonM3LK13uhPGIULF8HzXg8CgXE/fEnMlQ==
"@rari-capital/solmate@https://github.com/rari-capital/solmate.git#8f9b23f8838670afda0fd8983f2c41e8037ae6bc":
version "7.0.0-alpha.3"
resolved "https://github.com/rari-capital/solmate.git#8f9b23f8838670afda0fd8983f2c41e8037ae6bc"
"@resolver-engine/core@^0.3.3": "@resolver-engine/core@^0.3.3":
version "0.3.3" version "0.3.3"
resolved "https://registry.yarnpkg.com/@resolver-engine/core/-/core-0.3.3.tgz#590f77d85d45bc7ecc4e06c654f41345db6ca967" resolved "https://registry.yarnpkg.com/@resolver-engine/core/-/core-0.3.3.tgz#590f77d85d45bc7ecc4e06c654f41345db6ca967"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment