1) verify the name of your Contact Synchronized DE within Marketing Cloud.
Typically the name is something like "Contact_Salesforce", but in some circumstances (multi org configuration), also another suffix is added.
I have not seen it called "Contact" yet, although this would be the object name in Salesforce.
2) delete the "@" before the function, as EazyE said.
3) test your cloudpage code in an email first, to get error messages from the preview screen and debug accordingly
4) if your Cloud Page is hosted in a Child BU, you might have to do a workaround.
It is definitely correct that ENT. prefix has to be used to access the Parent BU context.
SET @firstname = lookup("ENT.nameOfYourSharedDE", "FirstName", "Id", @ContactID)
--- maybe you're already set now.
In case it still doesn't work.
AMPScript was limited in an update in February, so it should no longer possible to poll /update data extensions that are not Shared Data Extensions with this ENT. method from Child BUs.
I am not sure if Synchronized DEs are considered Shared. Just ran a test in a non-multi org environment, and it worked, but best be safe. This definitely should not work in Multi Org.
If you have been through all of the above steps and the DE persistently cannot be found, try a lookup with another, Shared DE.
If that works, get your Synchronized DE's content into a Shared Data Extension with an hourly SQL Query first. Then perform the lookup on that Shared DE. It will a bit more latency, but in some setups your only option.
My source for the February change is an SF Email quoted below
AMPScript and Enterprise Level Data Extension Changes
As part of our commitment to customer trust, we will introduce a change to how Enterprise Level Data Extensions are accessed at the Business Unit levels. Previously, all data extensions stored at the enterprise level could be accessed at the business unit level by using the "ENT." prefix.
Beginning February 9, 2019, AMPscript functions accessing data extensions without the sharing access will result in an error, potentially stopping messages from being sent.
AMPscript functions impacted include:
Lookup, LookupOrderedRows, LookupOrderedRowsCS, LookupRows, LookupRowsCS, UpsertData, Upsert DE, UpdateData, UpdateDE, ClaimRow, ClaimRowValue, DataExtensionRowCount, DeleteData, DeleteDE, InsertData and InsertDE
If you have messages with AMPscript in content that reference enterprise level data extensions that have not been shared with the business unit, this can be fixed by ensuring that these data extensions are stored in a shareable data extension folder. If your intent is to hide visibility of these data extensions, your message content will need to be modified.