diff --git a/abi/contracts/account/CMAccount.sol/CMAccount.json b/abi/contracts/account/CMAccount.sol/CMAccount.json index 15c02f1..eb7327e 100644 --- a/abi/contracts/account/CMAccount.sol/CMAccount.json +++ b/abi/contracts/account/CMAccount.sol/CMAccount.json @@ -1050,6 +1050,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "acceptCancellation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -1171,6 +1184,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "cancelCancellationProposal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -1219,6 +1245,29 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "refundAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "refundCurrency", + "type": "address" + } + ], + "name": "counterCancellationProposal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "getAllServiceHashes", @@ -1869,6 +1918,29 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "refundAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "refundCurrency", + "type": "address" + } + ], + "name": "initiateCancellation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { diff --git a/abi/contracts/booking-token/BookingTokenOperator.sol/BookingTokenOperator.json b/abi/contracts/booking-token/BookingTokenOperator.sol/BookingTokenOperator.json index 9e991bc..61015db 100644 --- a/abi/contracts/booking-token/BookingTokenOperator.sol/BookingTokenOperator.json +++ b/abi/contracts/booking-token/BookingTokenOperator.sol/BookingTokenOperator.json @@ -19,5 +19,44 @@ ], "name": "TokenApprovalFailed", "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "bookingToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getCancellationProposalStatus", + "outputs": [ + { + "internalType": "uint256", + "name": "refundAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "refundCurrency", + "type": "address" + }, + { + "internalType": "address", + "name": "initiatedBy", + "type": "address" + }, + { + "internalType": "bool", + "name": "isActive", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" } ] diff --git a/abi/contracts/booking-token/BookingTokenV2.sol/BookingTokenV2.json b/abi/contracts/booking-token/BookingTokenV2.sol/BookingTokenV2.json new file mode 100644 index 0000000..7db5759 --- /dev/null +++ b/abi/contracts/booking-token/BookingTokenV2.sol/BookingTokenV2.json @@ -0,0 +1,1574 @@ +[ + { + "inputs": [], + "name": "AccessControlBadConfirmation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "neededRole", + "type": "bytes32" + } + ], + "name": "AccessControlUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "ERC1967InvalidImplementation", + "type": "error" + }, + { + "inputs": [], + "name": "ERC1967NonPayable", + "type": "error" + }, + { + "inputs": [], + "name": "ERC721EnumerableForbiddenBatchMint", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "ERC721IncorrectOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ERC721InsufficientApproval", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC721InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "ERC721InvalidOperator", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "ERC721InvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC721InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC721InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ERC721NonexistentToken", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "ERC721OutOfBoundsIndex", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "expirationTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minExpirationTimestampDiff", + "type": "uint256" + } + ], + "name": "ExpirationTimestampTooSoon", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reservationPrice", + "type": "uint256" + } + ], + "name": "IncorrectPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "paymentToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + } + ], + "name": "InsufficientAllowance", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "NoActiveCancellationProposal", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + } + ], + "name": "NotAuthorizedToAcceptCancellation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + } + ], + "name": "NotAuthorizedToCancelProposal", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + } + ], + "name": "NotAuthorizedToCounterCancellation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + } + ], + "name": "NotAuthorizedToInitiateCancellation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "NotCMAccount", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expirationTimestamp", + "type": "uint256" + } + ], + "name": "ReservationExpired", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "reservedFor", + "type": "address" + }, + { + "internalType": "address", + "name": "buyer", + "type": "address" + } + ], + "name": "ReservationMismatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "supplier", + "type": "address" + } + ], + "name": "SupplierIsNotOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "TokenHasActiveCancellationProposal", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "reservedFor", + "type": "address" + } + ], + "name": "TokenIsReserved", + "type": "error" + }, + { + "inputs": [], + "name": "UUPSUnauthorizedCallContext", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "slot", + "type": "bytes32" + } + ], + "name": "UUPSUnsupportedProxiableUUID", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_fromTokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_toTokenId", + "type": "uint256" + } + ], + "name": "BatchMetadataUpdate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "acceptedBy", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "refundAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "refundCurrency", + "type": "address" + } + ], + "name": "CancellationAccepted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "counteredBy", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newRefundAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "newRefundCurrency", + "type": "address" + } + ], + "name": "CancellationCountered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "initiatedBy", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "refundAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "refundCurrency", + "type": "address" + } + ], + "name": "CancellationInitiated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "canceledBy", + "type": "address" + } + ], + "name": "CancellationProposalCanceled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "MetadataUpdate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "buyer", + "type": "address" + } + ], + "name": "TokenBought", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "reservedFor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "supplier", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "expirationTimestamp", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "contract IERC20", + "name": "paymentToken", + "type": "address" + } + ], + "name": "TokenReserved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_EXPIRATION_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "UPGRADER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "UPGRADE_INTERFACE_VERSION", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "acceptCancellation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "buyReservedToken", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "cancelCancellationProposal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newRefundAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "newRefundCurrency", + "type": "address" + } + ], + "name": "counterCancellationProposal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getCancellationProposalStatus", + "outputs": [ + { + "internalType": "uint256", + "name": "refundAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "refundCurrency", + "type": "address" + }, + { + "internalType": "address", + "name": "initiatedBy", + "type": "address" + }, + { + "internalType": "bool", + "name": "isActive", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getManagerAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getMinExpirationTimestampDiff", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getReservationPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "contract IERC20", + "name": "paymentToken", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "manager", + "type": "address" + }, + { + "internalType": "address", + "name": "defaultAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "upgrader", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "refundAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "refundCurrency", + "type": "address" + } + ], + "name": "initiateCancellation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "isCMAccount", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proxiableUUID", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "callerConfirmation", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "reservedFor", + "type": "address" + }, + { + "internalType": "string", + "name": "uri", + "type": "string" + }, + { + "internalType": "uint256", + "name": "expirationTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "contract IERC20", + "name": "paymentToken", + "type": "address" + } + ], + "name": "safeMintWithReservation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "manager", + "type": "address" + } + ], + "name": "setManagerAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "minExpirationTimestampDiff", + "type": "uint256" + } + ], + "name": "setMinExpirationTimestampDiff", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "tokenByIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "tokenOfOwnerByIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +] diff --git a/abi/contracts/booking-token/IBookingToken.sol/IBookingToken.json b/abi/contracts/booking-token/IBookingToken.sol/IBookingToken.json index f769ad4..be88947 100644 --- a/abi/contracts/booking-token/IBookingToken.sol/IBookingToken.json +++ b/abi/contracts/booking-token/IBookingToken.sol/IBookingToken.json @@ -1,4 +1,17 @@ [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "acceptCancellation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -12,6 +25,76 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "cancelCancellationProposal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newRefundAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "newRefundCurrency", + "type": "address" + } + ], + "name": "counterCancellationProposal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getCancellationProposalStatus", + "outputs": [ + { + "internalType": "uint256", + "name": "refundAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "refundCurrency", + "type": "address" + }, + { + "internalType": "address", + "name": "initiatedBy", + "type": "address" + }, + { + "internalType": "bool", + "name": "isActive", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -36,6 +119,29 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "refundAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "refundCurrency", + "type": "address" + } + ], + "name": "initiateCancellation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { diff --git a/contracts/account/CMAccount.sol b/contracts/account/CMAccount.sol index b4444b6..06ef756 100644 --- a/contracts/account/CMAccount.sol +++ b/contracts/account/CMAccount.sol @@ -761,4 +761,39 @@ contract CMAccount is function setGasMoneyWithdrawal(uint256 limit, uint256 period) public onlyRole(BOT_ADMIN_ROLE) { _setGasMoneyWithdrawal(limit, period); } + + /*************************************************** + * CANCELLATION * + ***************************************************/ + + // FIXME: Create a specific role for those + + function initiateCancellation( + uint256 tokenId, + uint256 refundAmount, + address refundCurrency + ) public onlyRole(SERVICE_ADMIN_ROLE) { + BookingTokenOperator.initiateCancellation(getBookingTokenAddress(), tokenId, refundAmount, refundCurrency); + } + + function acceptCancellation(uint256 tokenId) public onlyRole(SERVICE_ADMIN_ROLE) { + BookingTokenOperator.acceptCancellation(getBookingTokenAddress(), tokenId); + } + + function counterCancellationProposal( + uint256 tokenId, + uint256 refundAmount, + address refundCurrency + ) public onlyRole(SERVICE_ADMIN_ROLE) { + BookingTokenOperator.counterCancellationProposal( + getBookingTokenAddress(), + tokenId, + refundAmount, + refundCurrency + ); + } + + function cancelCancellationProposal(uint256 tokenId) public onlyRole(SERVICE_ADMIN_ROLE) { + BookingTokenOperator.cancelCancellationProposal(getBookingTokenAddress(), tokenId); + } } diff --git a/contracts/booking-token/BookingToken.sol b/contracts/booking-token/BookingToken.sol index 461c9b5..921f015 100644 --- a/contracts/booking-token/BookingToken.sol +++ b/contracts/booking-token/BookingToken.sol @@ -32,7 +32,7 @@ import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/ * Booking Tokens can have zero price, meaning that the payment will be done * off-chain. * - * When a token is minted with a reservation, it can not transferred until the + * When a token is minted with a reservation, it can not be transferred until the * expiration timestamp is reached or the token is bought. */ contract BookingToken is @@ -90,7 +90,7 @@ contract BookingToken is bytes32 private constant BookingTokenStorageLocation = 0x9db9d405bf15683ce835607b1f0b423dc1484d44bb9d5af64a483fa4afd82900; - function _getBookingTokenStorage() private pure returns (BookingTokenStorage storage $) { + function _getBookingTokenStorage() internal pure returns (BookingTokenStorage storage $) { assembly { $.slot := BookingTokenStorageLocation } @@ -251,7 +251,7 @@ contract BookingToken is uint256 expirationTimestamp, uint256 price, IERC20 paymentToken - ) public onlyCMAccount(msg.sender) { + ) public virtual onlyCMAccount(msg.sender) { // Require reservedFor to be a CM Account requireCMAccount(reservedFor); @@ -366,7 +366,7 @@ contract BookingToken is /** * @notice Check if the token is transferable */ - function checkTransferable(uint256 tokenId) internal { + function checkTransferable(uint256 tokenId) internal virtual { BookingTokenStorage storage $ = _getBookingTokenStorage(); TokenReservation memory reservation = $._reservations[tokenId]; diff --git a/contracts/booking-token/BookingTokenOperator.sol b/contracts/booking-token/BookingTokenOperator.sol index 24e21ae..a988a66 100644 --- a/contracts/booking-token/BookingTokenOperator.sol +++ b/contracts/booking-token/BookingTokenOperator.sol @@ -78,4 +78,37 @@ library BookingTokenOperator { IBookingToken(bookingToken).buyReservedToken{ value: price }(tokenId); } } + + function initiateCancellation( + address bookingToken, + uint256 tokenId, + uint256 refundAmount, + address refundCurrency + ) public { + IBookingToken(bookingToken).initiateCancellation(tokenId, refundAmount, refundCurrency); + } + + function acceptCancellation(address bookingToken, uint256 tokenId) public { + IBookingToken(bookingToken).acceptCancellation(tokenId); + } + + function counterCancellationProposal( + address bookingToken, + uint256 tokenId, + uint256 refundAmount, + address refundCurrency + ) public { + IBookingToken(bookingToken).counterCancellationProposal(tokenId, refundAmount, refundCurrency); + } + + function cancelCancellationProposal(address bookingToken, uint256 tokenId) public { + IBookingToken(bookingToken).cancelCancellationProposal(tokenId); + } + + function getCancellationProposalStatus( + address bookingToken, + uint256 tokenId + ) public view returns (uint256 refundAmount, address refundCurrency, address initiatedBy, bool isActive) { + return IBookingToken(bookingToken).getCancellationProposalStatus(tokenId); + } } diff --git a/contracts/booking-token/BookingTokenV2.sol b/contracts/booking-token/BookingTokenV2.sol new file mode 100644 index 0000000..5e9d98d --- /dev/null +++ b/contracts/booking-token/BookingTokenV2.sol @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import { BookingToken, IERC20 } from "./BookingToken.sol"; + +/** + * @title BookingTokenV2 + * @notice This contract extends BookingToken to add additional functionality. + * Specifically, it introduces a cancellation process after the token is bought. + */ +contract BookingTokenV2 is BookingToken { + /*************************************************** + * STORAGE * + ***************************************************/ + + struct CancellationProposal { + uint256 refundAmount; + address refundCurrency; + address initiatedBy; + bool isActive; + } + /// @custom:storage-location erc7201:camino.messenger.storage.BookingTokenV2 + struct BookingTokenV2Storage { + // Mapping to store the original supplier (minter) of each token + mapping(uint256 => address) _originalSupplier; + // Mapping to store the ongoing cancellation proposals for each token + mapping(uint256 => CancellationProposal) _cancellationProposals; + } + + // keccak256(abi.encode(uint256(keccak256("camino.messenger.storage.BookingTokenV2")) - 1)) & ~bytes32(uint256(0xff)); + bytes32 private constant BookingTokenV2StorageLocation = + 0x96683f18b5720c3d007c342e56e2dbf25d018d9c7f77c4217427b152a0bd6100; + + /** + * @notice Retrieves the BookingTokenV2 storage struct from the designated storage slot. + * + * @return $ The storage struct reference + */ + function _getBookingTokenV2Storage() private pure returns (BookingTokenV2Storage storage $) { + assembly { + $.slot := BookingTokenV2StorageLocation + } + } + + /*************************************************** + * EVENTS * + ***************************************************/ + + /** + * @notice Event emitted when a cancellation is initiated. + * + * @param tokenId token id + * @param initiatedBy address that initiated the cancellation + * @param refundAmount proposed refund amount + * @param refundCurrency ERC20 token address for the refund + */ + event CancellationInitiated( + uint256 indexed tokenId, + address indexed initiatedBy, + uint256 refundAmount, + address refundCurrency + ); + + /** + * @notice Event emitted when a cancellation is accepted. + * + * @param tokenId token id + * @param acceptedBy address that accepted the cancellation + * @param refundAmount final refund amount + * @param refundCurrency ERC20 token address for the refund + */ + event CancellationAccepted( + uint256 indexed tokenId, + address indexed acceptedBy, + uint256 refundAmount, + address refundCurrency + ); + + /** + * @notice Event emitted when a cancellation proposal is countered. + * + * @param tokenId token id + * @param counteredBy address that countered the proposal + * @param newRefundAmount new proposed refund amount + * @param newRefundCurrency new proposed ERC20 token address for the refund + */ + event CancellationCountered( + uint256 indexed tokenId, + address indexed counteredBy, + uint256 newRefundAmount, + address newRefundCurrency + ); + + /** + * @notice Event emitted when a cancellation proposal is canceled. + * + * @param tokenId token id + * @param canceledBy address that canceled the proposal + */ + event CancellationProposalCanceled(uint256 indexed tokenId, address indexed canceledBy); + + /*************************************************** + * ERRORS * + ***************************************************/ + + /** + * @notice Error for when the caller is not authorized to initiate a cancellation. + * + * @param caller The address of the caller + */ + error NotAuthorizedToInitiateCancellation(address caller); + + /** + * @notice Error for when there is no active cancellation proposal. + * + * @param tokenId The token id for which there is no active proposal + */ + error NoActiveCancellationProposal(uint256 tokenId); + + /** + * @notice Error for when the caller is not authorized to accept a cancellation. + * + * @param caller The address of the caller + */ + error NotAuthorizedToAcceptCancellation(address caller); + + /** + * @notice Error for when the caller is not authorized to counter a cancellation. + * + * @param caller The address of the caller + */ + error NotAuthorizedToCounterCancellation(address caller); + + /** + * @notice Error for when the caller is not authorized to cancel a proposal. + * + * @param caller The address of the caller + */ + error NotAuthorizedToCancelProposal(address caller); + + /** + * @notice Error for when a token has an active cancellation proposal and cannot be transferred. + * + * @param tokenId The token id that has an active cancellation proposal + */ + error TokenHasActiveCancellationProposal(uint256 tokenId); + + /*************************************************** + * FUNCTIONS * + ***************************************************/ + + /** + * @notice Mints a new token with a reservation for a specific address. + * + * @param reservedFor The CM Account address that can buy the token + * @param uri The URI of the token + * @param expirationTimestamp The expiration timestamp + * @param price The price of the token + * @param paymentToken The token used to pay for the reservation. If address(0) then native. + */ + function safeMintWithReservation( + address reservedFor, + string memory uri, + uint256 expirationTimestamp, + uint256 price, + IERC20 paymentToken + ) public override onlyCMAccount(msg.sender) { + super.safeMintWithReservation(reservedFor, uri, expirationTimestamp, price, paymentToken); + uint256 tokenId = _getBookingTokenStorage()._nextTokenId - 1; + _getBookingTokenV2Storage()._originalSupplier[tokenId] = msg.sender; + } + + /** + * @notice Initiates a cancellation for a bought token. + * + * @param tokenId The token id to initiate the cancellation for + * @param refundAmount The proposed refund amount in wei + * @param refundCurrency The ERC20 token address for the refund + */ + function initiateCancellation(uint256 tokenId, uint256 refundAmount, address refundCurrency) external { + address owner = _requireOwned(tokenId); + BookingTokenV2Storage storage $ = _getBookingTokenV2Storage(); + address supplier = $._originalSupplier[tokenId]; + if (msg.sender != owner && msg.sender != supplier) { + revert NotAuthorizedToInitiateCancellation(msg.sender); + } + + $._cancellationProposals[tokenId] = CancellationProposal({ + refundAmount: refundAmount, + refundCurrency: refundCurrency, + initiatedBy: msg.sender, + isActive: true + }); + + emit CancellationInitiated(tokenId, msg.sender, refundAmount, refundCurrency); + } + + /** + * @notice Accepts a cancellation proposal for a bought token. + * + * @param tokenId The token id to accept the cancellation for + */ + function acceptCancellation(uint256 tokenId) external { + BookingTokenV2Storage storage $ = _getBookingTokenV2Storage(); + address owner = _requireOwned(tokenId); + CancellationProposal memory proposal = $._cancellationProposals[tokenId]; + if (!proposal.isActive) { + revert NoActiveCancellationProposal(tokenId); + } + + address supplier = $._originalSupplier[tokenId]; + if (msg.sender == proposal.initiatedBy) { + revert NotAuthorizedToAcceptCancellation(msg.sender); + } + if ( + (proposal.initiatedBy == owner && msg.sender != supplier) || + (proposal.initiatedBy == supplier && msg.sender != owner) + ) { + revert NotAuthorizedToAcceptCancellation(msg.sender); + } + + // Finalize the cancellation + delete $._cancellationProposals[tokenId]; + _burn(tokenId); + + // Emit the cancellation accepted event + emit CancellationAccepted(tokenId, msg.sender, proposal.refundAmount, proposal.refundCurrency); + } + + /** + * @notice Counters a cancellation proposal with a new proposal. + * + * @param tokenId The token id to counter the cancellation for + * @param newRefundAmount The new proposed refund amount in wei + * @param newRefundCurrency The new ERC20 token address for the refund + */ + function counterCancellationProposal(uint256 tokenId, uint256 newRefundAmount, address newRefundCurrency) external { + BookingTokenV2Storage storage $ = _getBookingTokenV2Storage(); + address owner = _requireOwned(tokenId); + CancellationProposal storage proposal = $._cancellationProposals[tokenId]; + if (!proposal.isActive) { + revert NoActiveCancellationProposal(tokenId); + } + + address supplier = $._originalSupplier[tokenId]; + if (msg.sender == proposal.initiatedBy) { + revert NotAuthorizedToCounterCancellation(msg.sender); + } + if ( + (proposal.initiatedBy == owner && msg.sender != supplier) || + (proposal.initiatedBy == supplier && msg.sender != owner) + ) { + revert NotAuthorizedToCounterCancellation(msg.sender); + } + + // Update the proposal with the new values + proposal.refundAmount = newRefundAmount; + proposal.refundCurrency = newRefundCurrency; + + // Emit the countered proposal event + emit CancellationCountered(tokenId, msg.sender, newRefundAmount, newRefundCurrency); + } + + /** + * @notice Cancels an active cancellation proposal. Only the initiator can cancel. + * + * @param tokenId The token id for which to cancel the proposal + */ + function cancelCancellationProposal(uint256 tokenId) external { + BookingTokenV2Storage storage $ = _getBookingTokenV2Storage(); + CancellationProposal storage proposal = $._cancellationProposals[tokenId]; + if (!proposal.isActive) { + revert NoActiveCancellationProposal(tokenId); + } + if (msg.sender != proposal.initiatedBy) { + revert NotAuthorizedToCancelProposal(msg.sender); + } + + // Cancel the proposal + delete $._cancellationProposals[tokenId]; + + // Emit the cancellation proposal canceled event + emit CancellationProposalCanceled(tokenId, msg.sender); + } + + /** + * @notice Retrieves the current cancellation proposal status for a given token. + * + * @param tokenId The token id to check the proposal status for + * @return refundAmount The proposed refund amount + * @return refundCurrency The address of the proposed refund currency + * @return initiatedBy The address that initiated the cancellation + * @return isActive The status of the cancellation proposal + */ + function getCancellationProposalStatus( + uint256 tokenId + ) external view returns (uint256 refundAmount, address refundCurrency, address initiatedBy, bool isActive) { + BookingTokenV2Storage storage $ = _getBookingTokenV2Storage(); + CancellationProposal memory proposal = $._cancellationProposals[tokenId]; + return (proposal.refundAmount, proposal.refundCurrency, proposal.initiatedBy, proposal.isActive); + } + + /*************************************************** + * OVERRIDE FUNCTIONS * + ***************************************************/ + + /** + * @notice Checks if the token is transferable, considering reservation and cancellation status. + * + * @param tokenId The token id to check + */ + function checkTransferable(uint256 tokenId) internal override { + super.checkTransferable(tokenId); + BookingTokenV2Storage storage $ = _getBookingTokenV2Storage(); + if ($._cancellationProposals[tokenId].isActive) { + revert TokenHasActiveCancellationProposal(tokenId); + } + } +} diff --git a/contracts/booking-token/IBookingToken.sol b/contracts/booking-token/IBookingToken.sol index 41ee40e..dc96b4a 100644 --- a/contracts/booking-token/IBookingToken.sol +++ b/contracts/booking-token/IBookingToken.sol @@ -15,4 +15,49 @@ interface IBookingToken { function buyReservedToken(uint256 tokenId) external payable; function getReservationPrice(uint256 tokenId) external view returns (uint256 price, IERC20 paymentToken); + + /** + * @notice Initiates a cancellation for a bought token. + * + * @param tokenId The token id to initiate the cancellation for + * @param refundAmount The proposed refund amount in wei + * @param refundCurrency The ERC20 token address for the refund + */ + function initiateCancellation(uint256 tokenId, uint256 refundAmount, address refundCurrency) external; + + /** + * @notice Accepts a cancellation proposal for a bought token. + * + * @param tokenId The token id to accept the cancellation for + */ + function acceptCancellation(uint256 tokenId) external; + + /** + * @notice Counters a cancellation proposal with a new proposal. + * + * @param tokenId The token id to counter the cancellation for + * @param newRefundAmount The new proposed refund amount in wei + * @param newRefundCurrency The new ERC20 token address for the refund + */ + function counterCancellationProposal(uint256 tokenId, uint256 newRefundAmount, address newRefundCurrency) external; + + /** + * @notice Cancels an active cancellation proposal. Only the initiator can cancel. + * + * @param tokenId The token id for which to cancel the proposal + */ + function cancelCancellationProposal(uint256 tokenId) external; + + /** + * @notice Retrieves the current cancellation proposal status for a given token. + * + * @param tokenId The token id to check the proposal status for + * @return refundAmount The proposed refund amount + * @return refundCurrency The address of the proposed refund currency + * @return initiatedBy The address that initiated the cancellation + * @return isActive The status of the cancellation proposal + */ + function getCancellationProposalStatus( + uint256 tokenId + ) external view returns (uint256 refundAmount, address refundCurrency, address initiatedBy, bool isActive); } diff --git a/test/BookingToken.test.js b/test/BookingToken.test.js index 6650bdb..feb6429 100644 --- a/test/BookingToken.test.js +++ b/test/BookingToken.test.js @@ -594,4 +594,75 @@ describe("BookingToken", function () { .withArgs(0n, await distributorCMAccount.getAddress()); }); }); + describe("Cancellation", function () { + it("supplier: should initiate cancellation of a booking token correctly", async function () { + const { cmAccountManager, supplierCMAccount, distributorCMAccount, bookingToken } = + await loadFixture(deployBookingTokenFixture); + + const tokenURI = + "data:application/json;base64,eyJuYW1lIjoiQ2FtaW5vIE1lc3NlbmdlciBCb29raW5nVG9rZW4gVGVzdCJ9Cg=="; + + const expirationTimestamp = Math.floor(Date.now() / 1000) + 120; + + const price = ethers.parseEther("0.05"); + + /*************************************************** + * SUPPLIER * + ***************************************************/ + + // Grant BOOKING_OPERATOR_ROLE + const BOOKING_OPERATOR_ROLE = await supplierCMAccount.BOOKING_OPERATOR_ROLE(); + await expect( + supplierCMAccount + .connect(signers.cmAccountAdmin) + .grantRole(BOOKING_OPERATOR_ROLE, signers.btAdmin.address), + ).to.not.reverted; + + await expect( + await supplierCMAccount.connect(signers.btAdmin).mintBookingToken( + distributorCMAccount.getAddress(), // set reservedFor address to distributor CMAccount + tokenURI, // tokenURI + expirationTimestamp, // expiration + price, // price + ethers.ZeroAddress, // zero address + ), + ) + .to.be.emit(bookingToken, "TokenReserved") + .withArgs( + 0n, + distributorCMAccount.getAddress(), + supplierCMAccount.getAddress(), + expirationTimestamp, + price, + ethers.ZeroAddress, // zero address + ); + + // Check token ownership + expect(await bookingToken.ownerOf(0n)).to.equal(await supplierCMAccount.getAddress()); + + // Try to cancel the token + + const token_id = 0n; + const initiator = await supplierCMAccount.getAddress(); + const refundAmount = ethers.parseEther("0.045"); + const refundCurrency = ethers.ZeroAddress; + + await expect( + supplierCMAccount + .connect(signers.cmAccountAdmin) + .initiateCancellation(0n, refundAmount, refundCurrency), + ) + .to.emit(bookingToken, "CancellationInitiated") + .withArgs(token_id, initiator, refundAmount, refundCurrency); + + // Sanity check + expect(await bookingToken.getCancellationProposalStatus(token_id)).to.be.deep.equal([ + refundAmount, + refundCurrency, + initiator, + true, + ]); + }); + // FIXME: add tests for other cases + }); }); diff --git a/test/utils/fixtures.js b/test/utils/fixtures.js index af2a357..ec97a01 100644 --- a/test/utils/fixtures.js +++ b/test/utils/fixtures.js @@ -115,7 +115,7 @@ async function deployAndConfigureAllFixture() { // Deploy BookingToken - const BookingToken = await ethers.getContractFactory("BookingToken"); + const BookingToken = await ethers.getContractFactory("BookingTokenV2"); const bookingToken = await upgrades.deployProxy( BookingToken, [await cmAccountManager.getAddress(), signers.btAdmin.address, signers.btUpgrader.address],